vendor: commit existing golang vendor directory

This commit is contained in:
mappu 2018-12-31 18:21:15 +13:00
parent aa7557ed47
commit 8f443e1cfb
141 changed files with 30084 additions and 1 deletions

1
.gitignore vendored
View File

@ -4,4 +4,3 @@ nmdc-webfrontend.conf
clientpack/
_dist/
node_modules/
vendor/

14
vendor/code.ivysaur.me/libnmdc/.hgignore generated vendored Normal file
View File

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

20
vendor/code.ivysaur.me/libnmdc/.hgtags generated vendored Normal file
View File

@ -0,0 +1,20 @@
945ab4b16d05aa084f71bf5da9a3f687e0ec8bbd v0.1.0
02a360e95480b97ddad83add5db48b2766339a99 nmdc-log-service-1.0.0
137c1b65039e03c80379826a6efdfd808f6fbc8f v0.2.0
d8b64d5527c2a5e4d76872e5bc3d69f7646135c6 v0.3.0
fca41372e400853775b02e951f9db91d87f41adb nmdc-log-service-1.0.1
050b424a7c5d5a27c9323c8810f3afbead1f5b96 v0.4.0
da9f123633f9c28be6435ed7898139665d4c39d9 nmdc-log-service-1.0.2
75a78f6a78f249a2cd8aa3d29f7e5e6319b4e03b v0.5.0
4116422bb10229d887f9296970a166fa1ef8c5fd nmdc-log-service-1.0.3
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 nmdc-log-service-1.0.4
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 v0.6.0
71343a2c641a438206d30ea7e75dc89a11dbef00 v0.7.0
b0e57a5fcffdf4102d669db51a3648ddf66a0792 v0.8.0
e7c2c71ef24b386add728fad35fff4a996fccbac v0.9.0
3ecc037cf2d7080572fe87c2e39ecd153fb0e947 v0.10.0
5149ffe70ea8475e480b682345b31aa45a3352db v0.11.0
22b156a6fc2f6161765317f4ec9ab3731a26e0e2 v0.12.0
3ee0f4ea5142d66079a9500bdcd48a53bdcf362f v0.13.0
6422ed687cd308c339b6dc188bbe1034ed93f893 v0.14.0
84fb191007017862ffc37af68dcdace5d8c06eee v0.15.0

711
vendor/code.ivysaur.me/libnmdc/AdcProtocol.go generated vendored Normal file
View File

@ -0,0 +1,711 @@
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.hc.SayRaw("BINF " + this.escape(this.sid) + " " + this.ourINFO(true) + "\n")
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) 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) {
this.hc.SayRaw("BMSG " + this.sid + " " + this.escape(msg) + "\n")
}
func (this *AdcProtocol) SayPrivate(user, message string) {
if sid, ok := this.Nick2SID(user); ok {
this.hc.SayRaw("DMSG " + this.sid + " " + sid + " " + this.escape(message) + "\n")
} else {
this.logError(fmt.Errorf("Unknown user '%s'", user))
}
}
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)
}

80
vendor/code.ivysaur.me/libnmdc/AutodetectProtocol.go generated vendored Normal file
View File

@ -0,0 +1,80 @@
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) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayPublic(msg)
}
func (this *AutodetectProtocol) SayPrivate(user, message string) {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
this.realProto = NewNmdcProtocol(this.hc)
}
this.realProto.SayPrivate(user, message)
}
func (this *AutodetectProtocol) ProtoMessageSeparator() string {
this.realProtoMut.Lock()
defer this.realProtoMut.Unlock()
if this.realProto == nil {
return "|"
}
return this.realProto.ProtoMessageSeparator()
}

29
vendor/code.ivysaur.me/libnmdc/ConnectionMode.go generated vendored 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))
}
}

40
vendor/code.ivysaur.me/libnmdc/ConnectionState.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package libnmdc
import (
"net"
)
type ConnectionState int
const (
CONNECTIONSTATE_DISCONNECTED = 1
CONNECTIONSTATE_CONNECTING = 2 // Handshake in progress
CONNECTIONSTATE_CONNECTED = 3
)
func (cs ConnectionState) String() string {
switch cs {
case CONNECTIONSTATE_DISCONNECTED:
return "Disconnected"
case CONNECTIONSTATE_CONNECTING:
return "Connecting"
case CONNECTIONSTATE_CONNECTED:
return "Connected"
default:
return "?"
}
}
func checkIsNetTimeout(err error) bool {
if err == nil {
return false
}
switch err.(type) {
case net.Error:
return err.(net.Error).Timeout()
default:
return false
}
}

58
vendor/code.ivysaur.me/libnmdc/Example_test.go generated vendored 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
}

15
vendor/code.ivysaur.me/libnmdc/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,15 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/cxmcc/tiger"
packages = ["."]
revision = "bde35e2713d7f674987c2ecb21a6b0fc33749516"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "c88ee670a5600b482019325b6d6633bb6b5fe789596dc29ef809aa7bb013927b"
solver-name = "gps-cdcl"
solver-version = 1

26
vendor/code.ivysaur.me/libnmdc/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,26 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
branch = "master"
name = "github.com/cxmcc/tiger"

48
vendor/code.ivysaur.me/libnmdc/HubAddress.go generated vendored Normal file
View File

@ -0,0 +1,48 @@
package libnmdc
import (
"net/url"
"strings"
)
type HubAddress string
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: "",
Host: string(*this),
}
}
// Add default port if not specified
if !strings.ContainsRune(parsed.Host, ':') {
parsed.Host = parsed.Host + ":411"
}
return *parsed
}
func (this *HubAddress) IsSecure() bool {
parsed := this.parse()
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
}
}

229
vendor/code.ivysaur.me/libnmdc/HubConnection.go generated vendored Normal file
View File

@ -0,0 +1,229 @@
package libnmdc
import (
"crypto/tls"
"fmt"
"net"
"regexp"
"sync"
"time"
)
type HubConnection struct {
// Supplied parameters
Hco *HubConnectionOptions
// Current remote status
HubName string
State ConnectionState
usersMut sync.RWMutex
users map[string]UserInfo
userSIDs map[string]string
proto Protocol
// Event callback
processEvent func(HubEvent)
// Private state
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.usersMut.Lock()
defer this.usersMut.Unlock()
return cb(&this.users)
}
func (this *HubConnection) SayPublic(message string) {
this.proto.SayPublic(message)
}
func (this *HubConnection) SayPrivate(recipient string, message string) {
this.proto.SayPrivate(recipient, message)
}
func (this *HubConnection) UserExists(nick string) bool {
this.usersMut.RLock()
defer this.usersMut.RUnlock()
_, already_existed := this.users[nick]
return already_existed
}
func (this *HubConnection) UserCount() int {
this.usersMut.RLock()
defer this.usersMut.RUnlock()
return len(this.users)
}
func (this *HubConnection) userJoined_NameOnly(nick string) {
if !this.UserExists(nick) {
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) {
// 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
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 {
return ErrNotConnected
}
_, err := this.conn.Write([]byte(protocolCommand))
return err
}
func (this *HubConnection) SayKeepalive() error {
if !this.connValid {
return ErrNotConnected
}
return this.SayRaw(this.proto.ProtoMessageSeparator())
}
func (this *HubConnection) Disconnect() {
this.autoReconnect = false
if this.conn != nil {
this.conn.Close()
}
// A CONNECTIONSTATE_DISCONNECTED message will be emitted by the worker.
}
func (this *HubConnection) worker() {
var fullBuffer string
var err error = nil
var nbytes int = 0
for {
// If we're not connected, attempt reconnect
if this.conn == nil {
fullBuffer = "" // clear
if this.Hco.Address.IsSecure() {
this.conn, err = tls.Dial("tcp", this.Hco.Address.GetHostOnly(), &tls.Config{
InsecureSkipVerify: this.Hco.SkipVerifyTLS,
})
} else {
this.conn, err = net.Dial("tcp", this.Hco.Address.GetHostOnly())
}
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) {
// No data before read deadline
err = nil
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])
}
}
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
}
}
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))
}
}
// Maybe we disconnected
// Perform this check *last*, to ensure we've had a final shot at
// clearing out any queued messages
if err != nil {
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 {
time.Sleep(AUTO_RECONNECT_AFTER) // Wait before reconnect
continue
} else {
return // leave the worker for good
}
}
}
}

79
vendor/code.ivysaur.me/libnmdc/HubConnectionOptions.go generated vendored Normal file
View File

@ -0,0 +1,79 @@
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
NickPassword string
AdcPID string // blank: autogenerate
}
func NewPID() string {
pidBytes := make([]byte, 24)
n, err := rand.Read(pidBytes)
if err != nil {
panic(err) // Insufficient cryptographic randomness
}
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),
userSIDs: make(map[string]string),
autoReconnect: !this.SkipAutoReconnect,
}
return &hc
}
// 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 ConnectAsync(opts *HubConnectionOptions, onEvent chan HubEvent) *HubConnection {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent <- ev
}
go hc.worker()
return hc
}
// 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 ConnectSync(opts *HubConnectionOptions, onEvent func(hub *HubConnection, ev HubEvent)) {
hc := opts.prepareConnection()
hc.processEvent = func(ev HubEvent) {
onEvent(hc, ev)
}
hc.worker()
}

25
vendor/code.ivysaur.me/libnmdc/HubEvent.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package libnmdc
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
)
type HubEvent struct {
EventType HubEventType
Nick string
Message string
StateChange ConnectionState
UserCommand *UserCommand
}

407
vendor/code.ivysaur.me/libnmdc/NmdcProtocol.go generated vendored Normal file
View File

@ -0,0 +1,407 @@
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
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_SYSTEM_MESSAGE_FROM_CONN, Message: "Hub is full."})
case "$BadPass":
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_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) {
this.hc.SayRaw("<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) SayPrivate(recipient, message string) {
this.hc.SayRaw("$To: " + recipient + " From: " + this.hc.Hco.Self.Nick + " $<" + this.hc.Hco.Self.Nick + "> " + this.escape(message) + "|")
}
func (this *NmdcProtocol) sayInfo() {
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
vendor/code.ivysaur.me/libnmdc/NmdcProtocol_test.go generated vendored 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
}
}
}

31
vendor/code.ivysaur.me/libnmdc/Protocol.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
package libnmdc
type Protocol interface {
ProcessCommand(msg string)
SayPublic(string)
SayPrivate(user, message string)
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)
}
}

6
vendor/code.ivysaur.me/libnmdc/TODO.txt generated vendored Normal file
View File

@ -0,0 +1,6 @@
NMDC:
- Implement ZPipe ($ZOn)
ADC:
- Usercommands
- ???

27
vendor/code.ivysaur.me/libnmdc/UserCommand.go generated vendored 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
vendor/code.ivysaur.me/libnmdc/UserFlag.go generated vendored 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
)

53
vendor/code.ivysaur.me/libnmdc/UserInfo.go generated vendored Normal file
View File

@ -0,0 +1,53 @@
package libnmdc
// This structure represents a user connected to a hub.
type UserInfo struct {
Nick string
Description string
ClientTag string
ClientVersion string
Email string
ShareSize uint64
Flag UserFlag
Slots uint64
HubsUnregistered uint64
HubsRegistered uint64
HubsOperator uint64
IsOperator bool
UserInfo_NMDCOnly
UserInfo_ADCOnly
}
type UserInfo_NMDCOnly struct {
Speed string
IPAddress string
ConnectionMode ConnectionMode
}
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,
HubsUnregistered: 1,
UserInfo_NMDCOnly: UserInfo_NMDCOnly{
ConnectionMode: CONNECTIONMODE_PASSIVE,
},
}
}

98
vendor/code.ivysaur.me/libnmdc/__dist/README.txt generated vendored Normal file
View File

@ -0,0 +1,98 @@
An NMDC / ADC client protocol library for Golang.
Written in golang
Tags: nmdc
=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
=GO GET=
This package can be installed via go get: `go get code.ivysaur.me/libnmdc`
[go-get]code.ivysaur.me/libnmdc git https://git.ivysaur.me/code.ivysaur.me/libnmdc.git[/go-get]
=CHANGELOG=
2017-11-26 0.16
- 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
- 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
- Fix an issue with crashing on malformed IP addresses supplied by the hub
2017-02-09 0.13
- 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
- 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
- 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 r10
- Feature: Support `$UserCommand`
2016-08-27 r9
- Fix an issue with parsing MyINFO strings with zero-length speed descriptions
- Fix an issue with not storing updated profile information
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

28
vendor/code.ivysaur.me/libnmdc/libnmdc.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package libnmdc
import (
"errors"
"strconv"
"time"
)
const (
DEFAULT_CLIENT_TAG string = "libnmdc.go"
DEFAULT_CLIENT_VERSION string = "0.16"
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 ErrNotConnected error = errors.New("Not connected")
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
}
}

41
vendor/code.ivysaur.me/libnmdc/tth.go generated vendored 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
vendor/code.ivysaur.me/libnmdc/tth_test.go generated vendored 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]...)
}

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

@ -0,0 +1,144 @@
package tiger
import (
"fmt"
"io"
"strings"
"testing"
"unsafe"
)
type Test struct {
out string
in string
}
var golden = []Test{
{"3293ac630c13f0245f92bbb1766e16167a4e58492dde73f3", ""},
{"77befbef2e7ef8ab2ec8f93bf587a7fc613e247f5f247809", "a"},
{"2aab1484e8c158f2bfb8c5ff41b57a525129131c957b5f93", "abc"},
{"d981f8cb78201a950dcf3048751e441c517fca1aa55a29f6", "message digest"},
{"1714a472eee57d30040412bfcc55032a0b11602ff37beee9", "abcdefghijklmnopqrstuvwxyz"},
{"0f7bf9a19b9c58f2b7610df7e84f0ac3a71c631e7b53f78e", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"},
{"8dcea680a17583ee502ba38a3c368651890ffbccdc49a8cc", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"},
{"1c14795529fd9f207a958f84c52f11e887fa0cabdfd91bfd", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"},
{"cdf0990c5c6b6b0bddd63a75ed20e2d448bf44e15fde0df4", strings.Repeat("A", 1024)},
{"89292aee0f82842abc080c57b3aadd9ca84d66bf0cae77aa", strings.Repeat("A", 1025)},
}
func TestGolden(t *testing.T) {
for i := 0; i < len(golden); i++ {
g := golden[i]
c := New()
buf := make([]byte, len(g.in)+4)
for j := 0; j < 7; j++ {
if j < 2 {
io.WriteString(c, g.in)
} else if j == 2 {
io.WriteString(c, g.in[0:len(g.in)/2])
c.Sum(nil)
io.WriteString(c, g.in[len(g.in)/2:])
} else if j > 2 {
// test unaligned write
buf = buf[1:]
copy(buf, g.in)
c.Write(buf[:len(g.in)])
}
s := fmt.Sprintf("%x", c.Sum(nil))
if s != g.out {
t.Fatalf("tiger[%d](%s) = %s want %s", j, g.in, s, g.out)
}
c.Reset()
}
}
}
type WriteTest struct {
out int
in string
}
var writeTestVectors = []WriteTest{
{0, ""},
{1, "A"},
{2, "AA"},
{10, strings.Repeat("A", 10)},
{1024, strings.Repeat("A", 1024)},
{1025, strings.Repeat("A", 1025)},
{0, ""},
}
func TestWriteReturnsCorrectSize(t *testing.T) {
c := New()
for i := 0; i < len(writeTestVectors); i++ {
v := writeTestVectors[i]
b := []byte(v.in)
length, err := c.Write(b[:len(v.in)])
if length != v.out {
t.Fatalf("Write() = %d want %d", length, v.out)
}
if err != nil {
t.Fatalf("Write(%s) failed.", v.in)
}
}
}
func ExampleNew() {
h := New()
io.WriteString(h, "It's the eye of the tiger, it's the thrill of the fight")
io.WriteString(h, "Rising up to the challenge of our rival!")
fmt.Printf("%x", h.Sum(nil))
// Output: a7bbad36cc17918e399ae8ee893e4595e4d24e1639fe822c
}
func ExampleNew2() {
h := New2()
io.WriteString(h, "It's the eye of the tiger, it's the thrill of the fight")
io.WriteString(h, "Rising up to the challenge of our rival!")
fmt.Printf("%x", h.Sum(nil))
// Output: c86695c2a639506682de2c12c2d23b61a12db78ea1ee1001
}
var bench = New()
var buf = make([]byte, 8192+1)
var sum = make([]byte, bench.Size())
func benchmarkSize(b *testing.B, size int, unaligned bool) {
b.SetBytes(int64(size))
buf := buf
if unaligned {
if uintptr(unsafe.Pointer(&buf[0]))&(unsafe.Alignof(uint32(0))-1) == 0 {
buf = buf[1:]
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
bench.Reset()
bench.Write(buf[:size])
bench.Sum(sum[:0])
}
}
func BenchmarkHash8Bytes(b *testing.B) {
benchmarkSize(b, 8, false)
}
func BenchmarkHash1K(b *testing.B) {
benchmarkSize(b, 1024, false)
}
func BenchmarkHash8K(b *testing.B) {
benchmarkSize(b, 8192, false)
}
func BenchmarkHash8BytesUnaligned(b *testing.B) {
benchmarkSize(b, 8, true)
}
func BenchmarkHash1KUnaligned(b *testing.B) {
benchmarkSize(b, 1024, true)
}
func BenchmarkHash8KUnaligned(b *testing.B) {
benchmarkSize(b, 8192, true)
}

7
vendor/github.com/googollee/go-engine.io/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,7 @@
language: go
go: 1.5
install:
- go get "github.com/smartystreets/goconvey/convey"
- go get -v .
script:
- go test -race -v ./...

23
vendor/github.com/googollee/go-engine.io/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2014-2014 Googol Lee <i@googol.im>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

78
vendor/github.com/googollee/go-engine.io/README.md generated vendored Normal file
View File

@ -0,0 +1,78 @@
# go-engine.io
[![GoDoc](http://godoc.org/github.com/googollee/go-engine.io?status.svg)](http://godoc.org/github.com/googollee/go-engine.io) [![Build Status](https://travis-ci.org/googollee/go-engine.io.svg)](https://travis-ci.org/googollee/go-engine.io)
go-engine.io is the implement of engine.io in golang, which is transport-based cross-browser/cross-device bi-directional communication layer for [go-socket.io](https://github.com/googollee/go-socket.io).
It is compatible with node.js implement, and supported long-polling and websocket transport.
## Install
Install the package with:
```bash
go get github.com/googollee/go-engine.io
```
Import it with:
```go
import "github.com/googollee/go-engine.io"
```
and use `engineio` as the package name inside the code.
## Example
Please check example folder for details.
```go
package main
import (
"encoding/hex"
"io/ioutil"
"log"
"net/http"
"github.com/googollee/go-engine.io"
)
func main() {
server, err := engineio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
go func() {
for {
conn, _ := server.Accept()
go func() {
defer conn.Close()
for i := 0; i < 10; i++ {
t, r, _ := conn.NextReader()
b, _ := ioutil.ReadAll(r)
r.Close()
if t == engineio.MessageText {
log.Println(t, string(b))
} else {
log.Println(t, hex.EncodeToString(b))
}
w, _ := conn.NextWriter(t)
w.Write([]byte("pong"))
w.Close()
}
}()
}
}()
http.Handle("/engine.io/", server)
http.Handle("/", http.FileServer(http.Dir("./asset")))
log.Println("Serving at localhost:5000...")
log.Fatal(http.ListenAndServe(":5000", nil))
}
```
## License
The 3-clause BSD License - see LICENSE for more details

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<title>EIO Latency</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<h1>EIO Latency <span id="latency"></span></h1>
<h2 id="transport">(connecting)</h2>
<canvas id="chart" height="200"></canvas>
<script src="/index.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
body { margin: 0; padding: 0; font-family: Helvetica Neue; }
h1 { margin: 100px 100px 10px; }
h2 { color: #999; margin: 0 100px 30px; font-weight: normal; }
#latency { color: red; }

View File

@ -0,0 +1,60 @@
package main
import (
"encoding/hex"
"io/ioutil"
"log"
"net/http"
"time"
"github.com/googollee/go-engine.io"
)
func main() {
server, err := engineio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.SetPingInterval(time.Second * 2)
server.SetPingTimeout(time.Second * 3)
go func() {
for {
conn, _ := server.Accept()
go func() {
log.Println("connected:", conn.Id())
defer func() {
conn.Close()
log.Println("disconnected:", conn.Id())
}()
for {
t, r, err := conn.NextReader()
if err != nil {
return
}
b, err := ioutil.ReadAll(r)
if err != nil {
return
}
r.Close()
if t == engineio.MessageText {
log.Println(t, string(b))
} else {
log.Println(t, hex.EncodeToString(b))
}
w, err := conn.NextWriter(t)
if err != nil {
return
}
w.Write([]byte("pong"))
w.Close()
}
}()
}
}()
http.Handle("/engine.io/", server)
http.Handle("/", http.FileServer(http.Dir("./asset")))
log.Println("Serving at localhost:4000...")
log.Fatal(http.ListenAndServe(":4000", nil))
}

50
vendor/github.com/googollee/go-engine.io/ioutil.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package engineio
import (
"github.com/googollee/go-engine.io/parser"
"io"
"sync"
)
type connReader struct {
*parser.PacketDecoder
closeChan chan struct{}
}
func newConnReader(d *parser.PacketDecoder, closeChan chan struct{}) *connReader {
return &connReader{
PacketDecoder: d,
closeChan: closeChan,
}
}
func (r *connReader) Close() error {
if r.closeChan == nil {
return nil
}
r.closeChan <- struct{}{}
r.closeChan = nil
return nil
}
type connWriter struct {
io.WriteCloser
locker *sync.Mutex
}
func newConnWriter(w io.WriteCloser, locker *sync.Mutex) *connWriter {
return &connWriter{
WriteCloser: w,
locker: locker,
}
}
func (w *connWriter) Close() error {
defer func() {
if w.locker != nil {
w.locker.Unlock()
w.locker = nil
}
}()
return w.WriteCloser.Close()
}

123
vendor/github.com/googollee/go-engine.io/ioutil_test.go generated vendored Normal file
View File

@ -0,0 +1,123 @@
package engineio
import (
"bytes"
"github.com/googollee/go-engine.io/parser"
"io"
"sync"
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestConnIoutil(t *testing.T) {
Convey("Reader", t, func() {
Convey("Normal read", func() {
r := bytes.NewBufferString("\x34\xe6\xb5\x8b\xe8\xaf\x95")
decoder, err := parser.NewDecoder(r)
So(err, ShouldBeNil)
closeChan := make(chan struct{})
reader := newConnReader(decoder, closeChan)
b := make([]byte, 1024)
n, err := reader.Read(b)
So(err, ShouldBeNil)
So(string(b[:n]), ShouldEqual, "测试")
n, err = reader.Read(b)
So(err, ShouldEqual, io.EOF)
Convey("Wait close", func() {
check := make(chan int)
go func() {
err := reader.Close()
if err != nil {
t.Fatal(err)
}
check <- 1
}()
time.Sleep(time.Second / 10) // wait goroutine start
select {
case <-check:
So("should not run here", ShouldEqual, "")
default:
}
<-closeChan
time.Sleep(time.Second / 10) // wait goroutine end
select {
case <-check:
default:
So("should not run here", ShouldEqual, "")
}
Convey("Close again", func() {
err := reader.Close()
So(err, ShouldBeNil)
})
})
})
})
Convey("Wrtier", t, func() {
Convey("Normal write", func() {
locker := sync.Mutex{}
w := bytes.NewBuffer(nil)
locker.Lock()
writer := newConnWriter(writeCloser{w}, &locker)
_, err := writer.Write([]byte("abc"))
So(err, ShouldBeNil)
So(w.String(), ShouldEqual, "abc")
writer.Close()
})
Convey("Sync", func() {
locker := sync.Mutex{}
w1 := bytes.NewBuffer(nil)
locker.Lock()
writer1 := newConnWriter(writeCloser{w1}, &locker)
check := make(chan int)
go func() {
w2 := bytes.NewBuffer(nil)
locker.Lock()
writer2 := newConnWriter(writeCloser{w2}, &locker)
defer writer2.Close()
check <- 1
}()
time.Sleep(time.Second / 10)
select {
case <-check:
So("should not run here", ShouldEqual, "")
default:
}
err := writer1.Close()
So(err, ShouldBeNil)
time.Sleep(time.Second / 10) // wait goroutine end
select {
case <-check:
default:
So("should not run here", ShouldEqual, "")
}
Convey("Close again", func() {
err := writer1.Close()
So(err, ShouldBeNil)
})
})
})
}
type writeCloser struct {
io.Writer
}
func (w writeCloser) Close() error {
return nil
}

View File

@ -0,0 +1,8 @@
package message
type MessageType int
const (
MessageText MessageType = iota
MessageBinary
)

View File

@ -0,0 +1,45 @@
package parser
import (
"io"
)
type limitReader struct {
io.Reader
remain int
}
func newLimitReader(r io.Reader, limit int) *limitReader {
return &limitReader{
Reader: r,
remain: limit,
}
}
func (r *limitReader) Read(b []byte) (int, error) {
if r.remain == 0 {
return 0, io.EOF
}
if len(b) > r.remain {
b = b[:r.remain]
}
n, err := r.Reader.Read(b)
r.remain -= n
return n, err
}
func (r *limitReader) Close() error {
if r.remain > 0 {
b := make([]byte, 10240)
for {
_, err := r.Read(b)
if err == io.EOF {
break
}
if err != nil {
return err
}
}
}
return nil
}

View File

@ -0,0 +1,58 @@
package parser
import (
"bytes"
"errors"
"io"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestLimitReader(t *testing.T) {
Convey("Read to limit", t, func() {
b := bytes.NewBufferString("1234567890")
r := newLimitReader(b, 5)
p := make([]byte, 1024)
n, err := r.Read(p)
So(err, ShouldBeNil)
So(string(p[:n]), ShouldEqual, "12345")
n, err = r.Read(p)
So(err, ShouldEqual, io.EOF)
err = r.Close()
So(err, ShouldBeNil)
So(b.String(), ShouldEqual, "67890")
})
Convey("Read some and close", t, func() {
b := bytes.NewBufferString("1234567890")
r := newLimitReader(b, 5)
p := make([]byte, 3)
n, err := r.Read(p)
So(err, ShouldBeNil)
So(string(p[:n]), ShouldEqual, "123")
err = r.Close()
So(err, ShouldBeNil)
So(b.String(), ShouldEqual, "67890")
err = r.Close()
So(err, ShouldBeNil)
})
Convey("Close with error", t, func() {
er := errorReader{}
r := newLimitReader(er, 5)
err := r.Close()
So(err, ShouldNotBeNil)
})
}
type errorReader struct{}
func (r errorReader) Read(p []byte) (int, error) {
return 0, errors.New("error")
}
func (r errorReader) Close() error {
return errors.New("error")
}

View File

@ -0,0 +1,191 @@
package parser
import (
"encoding/base64"
"fmt"
"io"
"github.com/googollee/go-engine.io/message"
)
// PacketType is the type of packet
type PacketType string
const (
OPEN PacketType = "open"
CLOSE PacketType = "close"
PING PacketType = "ping"
PONG PacketType = "pong"
MESSAGE PacketType = "message"
UPGRADE PacketType = "upgrade"
NOOP PacketType = "noop"
)
func ByteToType(b byte) (PacketType, error) {
switch b {
case 0:
return OPEN, nil
case 1:
return CLOSE, nil
case 2:
return PING, nil
case 3:
return PONG, nil
case 4:
return MESSAGE, nil
case 5:
return UPGRADE, nil
case 6:
return NOOP, nil
}
return NOOP, fmt.Errorf("invalid byte 0x%x", b)
}
// Byte return the byte of type
func (t PacketType) Byte() byte {
switch t {
case OPEN:
return 0
case CLOSE:
return 1
case PING:
return 2
case PONG:
return 3
case MESSAGE:
return 4
case UPGRADE:
return 5
}
return 6
}
// packetEncoder is the encoder which encode the packet.
type PacketEncoder struct {
closer io.Closer
w io.Writer
}
// NewStringEncoder return the encoder which encode type t to writer w, as string.
func NewStringEncoder(w io.Writer, t PacketType) (*PacketEncoder, error) {
return newEncoder(w, t.Byte()+'0')
}
// NewBinaryEncoder return the encoder which encode type t to writer w, as binary.
func NewBinaryEncoder(w io.Writer, t PacketType) (*PacketEncoder, error) {
return newEncoder(w, t.Byte())
}
func newEncoder(w io.Writer, t byte) (*PacketEncoder, error) {
if _, err := w.Write([]byte{t}); err != nil {
return nil, err
}
closer, ok := w.(io.Closer)
if !ok {
closer = nil
}
return &PacketEncoder{
closer: closer,
w: w,
}, nil
}
// NewB64Encoder return the encoder which encode type t to writer w, as string. When write binary, it uses base64.
func NewB64Encoder(w io.Writer, t PacketType) (*PacketEncoder, error) {
_, err := w.Write([]byte{'b', t.Byte() + '0'})
if err != nil {
return nil, err
}
base := base64.NewEncoder(base64.StdEncoding, w)
return &PacketEncoder{
closer: base,
w: base,
}, nil
}
// Write writes bytes p.
func (e *PacketEncoder) Write(p []byte) (int, error) {
return e.w.Write(p)
}
// Close closes the encoder.
func (e *PacketEncoder) Close() error {
if e.closer != nil {
return e.closer.Close()
}
return nil
}
// packetDecoder is the decoder which decode data to packet.
type PacketDecoder struct {
closer io.Closer
r io.Reader
t PacketType
msgType message.MessageType
}
// NewDecoder return the decoder which decode from reader r.
func NewDecoder(r io.Reader) (*PacketDecoder, error) {
var closer io.Closer
if limit, ok := r.(*limitReader); ok {
closer = limit
}
defer func() {
if closer != nil {
closer.Close()
}
}()
b := []byte{0xff}
if _, err := r.Read(b); err != nil {
return nil, err
}
msgType := message.MessageText
if b[0] == 'b' {
if _, err := r.Read(b); err != nil {
return nil, err
}
r = base64.NewDecoder(base64.StdEncoding, r)
msgType = message.MessageBinary
}
if b[0] >= '0' {
b[0] = b[0] - '0'
} else {
msgType = message.MessageBinary
}
t, err := ByteToType(b[0])
if err != nil {
return nil, err
}
ret := &PacketDecoder{
closer: closer,
r: r,
t: t,
msgType: msgType,
}
closer = nil
return ret, nil
}
// Read reads packet data to bytes p.
func (d *PacketDecoder) Read(p []byte) (int, error) {
return d.r.Read(p)
}
// Type returns the type of packet.
func (d *PacketDecoder) Type() PacketType {
return d.t
}
// MessageType returns the type of message, binary or string.
func (d *PacketDecoder) MessageType() message.MessageType {
return d.msgType
}
// Close closes the decoder.
func (d *PacketDecoder) Close() error {
if d.closer != nil {
return d.closer.Close()
}
return nil
}

View File

@ -0,0 +1,297 @@
package parser
import (
"bytes"
"io"
"testing"
"github.com/googollee/go-engine.io/message"
. "github.com/smartystreets/goconvey/convey"
)
func TestPacketType(t *testing.T) {
Convey("Byte to type", t, func() {
Convey("Open", func() {
t, err := ByteToType(0)
So(err, ShouldBeNil)
So(t, ShouldEqual, OPEN)
})
Convey("Close", func() {
t, err := ByteToType(1)
So(err, ShouldBeNil)
So(t, ShouldEqual, CLOSE)
})
Convey("Ping", func() {
t, err := ByteToType(2)
So(err, ShouldBeNil)
So(t, ShouldEqual, PING)
})
Convey("Pong", func() {
t, err := ByteToType(3)
So(err, ShouldBeNil)
So(t, ShouldEqual, PONG)
})
Convey("Message", func() {
t, err := ByteToType(4)
So(err, ShouldBeNil)
So(t, ShouldEqual, MESSAGE)
})
Convey("Upgrade", func() {
t, err := ByteToType(5)
So(err, ShouldBeNil)
So(t, ShouldEqual, UPGRADE)
})
Convey("Noop", func() {
t, err := ByteToType(6)
So(err, ShouldBeNil)
So(t, ShouldEqual, NOOP)
})
Convey("Error", func() {
_, err := ByteToType(7)
So(err, ShouldNotBeNil)
})
})
Convey("Type to byte", t, func() {
Convey("Open", func() {
So(OPEN.Byte(), ShouldEqual, 0)
})
Convey("Close", func() {
So(CLOSE.Byte(), ShouldEqual, 1)
})
Convey("Ping", func() {
So(PING.Byte(), ShouldEqual, 2)
})
Convey("Pong", func() {
So(PONG.Byte(), ShouldEqual, 3)
})
Convey("Message", func() {
So(MESSAGE.Byte(), ShouldEqual, 4)
})
Convey("Upgrade", func() {
So(UPGRADE.Byte(), ShouldEqual, 5)
})
Convey("Noop", func() {
So(NOOP.Byte(), ShouldEqual, 6)
})
})
}
func TestStringParser(t *testing.T) {
type Test struct {
name string
t PacketType
data []byte
output string
}
var tests = []Test{
{"without data", OPEN, nil, "0"},
{"with data", MESSAGE, []byte("测试"), "\x34\xe6\xb5\x8b\xe8\xaf\x95"},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
Convey("Given a packet type "+test.name, t, func() {
Convey("Create encoder", func() {
encoder, err := NewStringEncoder(buf, test.t)
So(err, ShouldBeNil)
So(encoder, ShouldImplement, (*io.WriteCloser)(nil))
Convey("Encoded", func() {
for d := test.data; len(d) > 0; {
n, err := encoder.Write(d)
So(err, ShouldBeNil)
d = d[n:]
}
Convey("End", func() {
err := encoder.Close()
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, test.output)
})
})
})
Convey("Create decoder", func() {
decoder, err := NewDecoder(buf)
So(err, ShouldBeNil)
So(decoder, ShouldImplement, (*io.ReadCloser)(nil))
So(decoder.MessageType(), ShouldEqual, message.MessageText)
Convey("Decoded", func() {
So(decoder.Type(), ShouldEqual, test.t)
decoded := make([]byte, len(test.data)+1)
n, err := decoder.Read(decoded)
if n > 0 {
So(err, ShouldBeNil)
So(decoded[:n], ShouldResemble, test.data)
}
Convey("End", func() {
_, err := decoder.Read(decoded[:])
So(err, ShouldEqual, io.EOF)
})
})
})
})
}
}
func TestBinaryParser(t *testing.T) {
type Test struct {
name string
t PacketType
data []byte
output string
}
var tests = []Test{
{"without data", OPEN, nil, "\x00"},
{"with data", MESSAGE, []byte("测试"), "\x04\xe6\xb5\x8b\xe8\xaf\x95"},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
Convey("Given a packet type "+test.name, t, func() {
Convey("Create Encoder", func() {
encoder, err := NewBinaryEncoder(buf, test.t)
So(err, ShouldBeNil)
So(encoder, ShouldImplement, (*io.WriteCloser)(nil))
Convey("Encoded", func() {
for d := test.data; len(d) > 0; {
n, err := encoder.Write(d)
So(err, ShouldBeNil)
d = d[n:]
}
Convey("End", func() {
err := encoder.Close()
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, test.output)
})
})
})
Convey("Create decoder", func() {
decoder, err := NewDecoder(buf)
So(err, ShouldBeNil)
So(decoder, ShouldImplement, (*io.ReadCloser)(nil))
So(decoder.MessageType(), ShouldEqual, message.MessageBinary)
Convey("Decoded", func() {
So(decoder.Type(), ShouldEqual, test.t)
decoded := make([]byte, len(test.data)+1)
n, err := decoder.Read(decoded[:])
if n > 0 {
So(err, ShouldBeNil)
So(decoded[:n], ShouldResemble, test.data)
}
Convey("End", func() {
_, err := decoder.Read(decoded[:])
So(err, ShouldEqual, io.EOF)
})
})
})
})
}
}
func TestBase64Parser(t *testing.T) {
type Test struct {
name string
t PacketType
data []byte
output string
}
var tests = []Test{
{"without data", OPEN, nil, "b0"},
{"with data", MESSAGE, []byte("测试"), "b45rWL6K+V"},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
Convey("Given a packet type "+test.name, t, func() {
Convey("Create Encoder", func() {
encoder, err := NewB64Encoder(buf, test.t)
So(err, ShouldBeNil)
So(encoder, ShouldImplement, (*io.WriteCloser)(nil))
Convey("Encoded", func() {
for d := test.data; len(d) > 0; {
n, err := encoder.Write(d)
So(err, ShouldBeNil)
d = d[n:]
}
Convey("End", func() {
err := encoder.Close()
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, test.output)
})
})
})
Convey("Create decoder", func() {
decoder, err := NewDecoder(buf)
So(err, ShouldBeNil)
So(decoder, ShouldImplement, (*io.ReadCloser)(nil))
So(decoder.MessageType(), ShouldEqual, message.MessageBinary)
Convey("Decoded", func() {
So(decoder.Type(), ShouldEqual, test.t)
decoded := make([]byte, len(test.data)+1)
n, err := decoder.Read(decoded[:])
if n > 0 {
So(err, ShouldBeNil)
So(decoded[:n], ShouldResemble, test.data)
}
Convey("End", func() {
_, err := decoder.Read(decoded[:])
So(err, ShouldEqual, io.EOF)
})
})
})
})
}
}
func TestLimitReaderDecoder(t *testing.T) {
Convey("Test decoder with limit reader", t, func() {
buf := bytes.NewBufferString("\x34\xe6\xb5\x8b\xe8\xaf\x95123")
reader := newLimitReader(buf, 7)
decoder, err := NewDecoder(reader)
So(err, ShouldBeNil)
So(decoder.Type(), ShouldEqual, MESSAGE)
err = decoder.Close()
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, "123")
})
}

View File

@ -0,0 +1,3 @@
package parser
const Protocol = 3

View File

@ -0,0 +1,170 @@
package parser
import (
"bufio"
"bytes"
"fmt"
"io"
"strconv"
"sync"
)
// payloadEncoder is the encoder to encode packets as payload. It can be used in multi-thread.
type PayloadEncoder struct {
buffers [][]byte
locker sync.Mutex
isString bool
}
// NewStringPayloadEncoder returns the encoder which encode as string.
func NewStringPayloadEncoder() *PayloadEncoder {
return &PayloadEncoder{
isString: true,
}
}
// NewStringPayloadEncoder returns the encoder which encode as binary.
func NewBinaryPayloadEncoder() *PayloadEncoder {
return &PayloadEncoder{
isString: false,
}
}
type encoder struct {
*PacketEncoder
buf *bytes.Buffer
binaryPrefix string
payload *PayloadEncoder
}
func (e encoder) Close() error {
if err := e.PacketEncoder.Close(); err != nil {
return err
}
var buffer []byte
if e.payload.isString {
buffer = []byte(fmt.Sprintf("%d:%s", e.buf.Len(), e.buf.String()))
} else {
buffer = []byte(fmt.Sprintf("%s%d", e.binaryPrefix, e.buf.Len()))
for i, n := 0, len(buffer); i < n; i++ {
buffer[i] = buffer[i] - '0'
}
buffer = append(buffer, 0xff)
buffer = append(buffer, e.buf.Bytes()...)
}
e.payload.locker.Lock()
e.payload.buffers = append(e.payload.buffers, buffer)
e.payload.locker.Unlock()
return nil
}
// NextString returns the encoder with packet type t and encode as string.
func (e *PayloadEncoder) NextString(t PacketType) (io.WriteCloser, error) {
buf := bytes.NewBuffer(nil)
pEncoder, err := NewStringEncoder(buf, t)
if err != nil {
return nil, err
}
return encoder{
PacketEncoder: pEncoder,
buf: buf,
binaryPrefix: "0",
payload: e,
}, nil
}
// NextBinary returns the encoder with packet type t and encode as binary.
func (e *PayloadEncoder) NextBinary(t PacketType) (io.WriteCloser, error) {
buf := bytes.NewBuffer(nil)
var pEncoder *PacketEncoder
var err error
if e.isString {
pEncoder, err = NewB64Encoder(buf, t)
} else {
pEncoder, err = NewBinaryEncoder(buf, t)
}
if err != nil {
return nil, err
}
return encoder{
PacketEncoder: pEncoder,
buf: buf,
binaryPrefix: "1",
payload: e,
}, nil
}
// EncodeTo writes encoded payload to writer w. It will clear the buffer of encoder.
func (e *PayloadEncoder) EncodeTo(w io.Writer) error {
e.locker.Lock()
buffers := e.buffers
e.buffers = nil
e.locker.Unlock()
for _, b := range buffers {
for len(b) > 0 {
n, err := w.Write(b)
if err != nil {
return err
}
b = b[n:]
}
}
return nil
}
//IsString returns true if payload encode to string, otherwise returns false.
func (e *PayloadEncoder) IsString() bool {
return e.isString
}
// payloadDecoder is the decoder to decode payload.
type PayloadDecoder struct {
r *bufio.Reader
}
// NewPaylaodDecoder returns the payload decoder which read from reader r.
func NewPayloadDecoder(r io.Reader) *PayloadDecoder {
br, ok := r.(*bufio.Reader)
if !ok {
br = bufio.NewReader(r)
}
return &PayloadDecoder{
r: br,
}
}
// Next returns the packet decoder. Make sure it will be closed after used.
func (d *PayloadDecoder) Next() (*PacketDecoder, error) {
firstByte, err := d.r.Peek(1)
if err != nil {
return nil, err
}
isBinary := firstByte[0] < '0'
delim := byte(':')
if isBinary {
d.r.ReadByte()
delim = 0xff
}
line, err := d.r.ReadBytes(delim)
if err != nil {
return nil, err
}
l := len(line)
if l < 1 {
return nil, fmt.Errorf("invalid input")
}
lenByte := line[:l-1]
if isBinary {
for i, n := 0, l; i < n; i++ {
line[i] = line[i] + '0'
}
}
packetLen, err := strconv.ParseInt(string(lenByte), 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid input")
}
return NewDecoder(newLimitReader(d.r, int(packetLen)))
}

View File

@ -0,0 +1,212 @@
package parser
import (
"bytes"
"io"
"runtime"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestStringPayload(t *testing.T) {
type packet struct {
Type PacketType
Data []byte
IsString bool
}
type Test struct {
name string
packets []packet
output string
}
var tests = []Test{
{"all in one", []packet{packet{OPEN, nil, true}, packet{MESSAGE, []byte("测试"), true}, packet{MESSAGE, []byte("测试"), false}}, "\x31\x3a\x30\x37\x3a\x34\xe6\xb5\x8b\xe8\xaf\x95\x31\x30\x3a\x62\x34\x35\x72\x57\x4c\x36\x4b\x2b\x56"},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
Convey("Given an array of packet "+test.name, t, func() {
Convey("Create encoder", func() {
encoder := NewStringPayloadEncoder()
So(encoder.IsString(), ShouldBeTrue)
Convey("Encoded", func() {
for _, p := range test.packets {
var e io.WriteCloser
var err error
if p.IsString {
e, err = encoder.NextString(p.Type)
} else {
e, err = encoder.NextBinary(p.Type)
}
So(err, ShouldBeNil)
for d := p.Data; len(d) > 0; {
n, err := e.Write(d)
So(err, ShouldBeNil)
d = d[n:]
}
err = e.Close()
So(err, ShouldBeNil)
}
Convey("End", func() {
err := encoder.EncodeTo(buf)
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, test.output)
})
})
})
Convey("Create decoder", func() {
decoder := NewPayloadDecoder(buf)
Convey("Decode", func() {
for i := 0; ; i++ {
d, err := decoder.Next()
if err == io.EOF {
break
}
So(err, ShouldBeNil)
So(d.Type(), ShouldEqual, test.packets[i].Type)
if l := len(test.packets[i].Data); l > 0 {
buf := make([]byte, len(test.packets[i].Data)+1)
n, err := d.Read(buf)
if n > 0 {
So(err, ShouldBeNil)
So(buf[:n], ShouldResemble, test.packets[i].Data)
}
_, err = d.Read(buf)
So(err, ShouldEqual, io.EOF)
}
err = d.Close()
So(err, ShouldBeNil)
}
})
})
})
}
}
func TestBinaryPayload(t *testing.T) {
type packet struct {
Type PacketType
Data []byte
IsString bool
}
type Test struct {
name string
packets []packet
output string
}
var tests = []Test{
{"all in one", []packet{packet{OPEN, nil, true}, packet{MESSAGE, []byte("测试"), true}, packet{MESSAGE, []byte("测试"), false}}, "\x00\x01\xff\x30\x00\x07\xff\x34\xe6\xb5\x8b\xe8\xaf\x95\x01\x07\xff\x04\xe6\xb5\x8b\xe8\xaf\x95"},
}
for _, test := range tests {
buf := bytes.NewBuffer(nil)
Convey("Given an array of packet "+test.name, t, func() {
Convey("Create encoder", func() {
encoder := NewBinaryPayloadEncoder()
So(encoder.IsString(), ShouldBeFalse)
Convey("Encoded", func() {
for _, p := range test.packets {
var e io.WriteCloser
var err error
if p.IsString {
e, err = encoder.NextString(p.Type)
} else {
e, err = encoder.NextBinary(p.Type)
}
So(err, ShouldBeNil)
for d := p.Data; len(d) > 0; {
n, err := e.Write(d)
So(err, ShouldBeNil)
d = d[n:]
}
err = e.Close()
So(err, ShouldBeNil)
}
Convey("End", func() {
err := encoder.EncodeTo(buf)
So(err, ShouldBeNil)
So(buf.String(), ShouldEqual, test.output)
})
})
})
Convey("Create decoder", func() {
decoder := NewPayloadDecoder(buf)
Convey("Decode", func() {
for i := 0; ; i++ {
d, err := decoder.Next()
if err == io.EOF {
break
}
So(err, ShouldBeNil)
So(d.Type(), ShouldEqual, test.packets[i].Type)
if l := len(test.packets[i].Data); l > 0 {
buf := make([]byte, len(test.packets[i].Data)+1)
n, err := d.Read(buf)
if n > 0 {
So(err, ShouldBeNil)
So(buf[:n], ShouldResemble, test.packets[i].Data)
}
_, err = d.Read(buf)
So(err, ShouldEqual, io.EOF)
}
err = d.Close()
So(err, ShouldBeNil)
}
})
})
})
}
}
func TestParallelEncode(t *testing.T) {
prev := runtime.GOMAXPROCS(10)
defer runtime.GOMAXPROCS(prev)
Convey("Test parallel encode", t, func() {
c := make(chan int)
max := 1000
buf1 := bytes.NewBuffer(nil)
buf2 := bytes.NewBuffer(nil)
encoder := NewStringPayloadEncoder()
for i := 0; i < max; i++ {
go func() {
e, _ := encoder.NextString(MESSAGE)
e.Write([]byte("1234"))
e.Close()
c <- 1
}()
}
for i := 0; i < max/2; i++ {
<-c
}
err := encoder.EncodeTo(buf1)
So(err, ShouldBeNil)
for i := 0; i < max/2; i++ {
<-c
}
err = encoder.EncodeTo(buf2)
So(err, ShouldBeNil)
for s := buf1.String(); len(s) > 0; {
So(s, ShouldStartWith, "5:41234")
s = s[len("5:41234"):]
}
for s := buf2.String(); len(s) > 0; {
So(s, ShouldStartWith, "5:41234")
s = s[len("5:41234"):]
}
})
}

View File

@ -0,0 +1,149 @@
package polling
import (
"bytes"
"fmt"
"github.com/googollee/go-engine.io/message"
"io"
"io/ioutil"
"net/http"
"net/url"
"time"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
)
type client struct {
req http.Request
url url.URL
seq uint
getResp *http.Response
postResp *http.Response
resp *http.Response
payloadDecoder *parser.PayloadDecoder
payloadEncoder *parser.PayloadEncoder
client *http.Client
state state
}
func NewClient(r *http.Request) (transport.Client, error) {
newEncoder := parser.NewBinaryPayloadEncoder
if _, ok := r.URL.Query()["b64"]; ok {
newEncoder = parser.NewStringPayloadEncoder
}
ret := &client{
req: *r,
url: *r.URL,
seq: 0,
payloadEncoder: newEncoder(),
client: http.DefaultClient,
state: stateNormal,
}
return ret, nil
}
func (c *client) Response() *http.Response {
return c.resp
}
func (c *client) NextReader() (*parser.PacketDecoder, error) {
if c.state != stateNormal {
return nil, io.EOF
}
if c.payloadDecoder != nil {
ret, err := c.payloadDecoder.Next()
if err != io.EOF {
return ret, err
}
c.getResp.Body.Close()
c.payloadDecoder = nil
}
req := c.getReq()
req.Method = "GET"
var err error
c.getResp, err = c.client.Do(req)
if err != nil {
return nil, err
}
if c.resp == nil {
c.resp = c.getResp
}
c.payloadDecoder = parser.NewPayloadDecoder(c.getResp.Body)
return c.payloadDecoder.Next()
}
func (c *client) NextWriter(messageType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
if c.state != stateNormal {
return nil, io.EOF
}
next := c.payloadEncoder.NextBinary
if messageType == message.MessageText {
next = c.payloadEncoder.NextString
}
w, err := next(packetType)
if err != nil {
return nil, err
}
return newClientWriter(c, w), nil
}
func (c *client) Close() error {
if c.state != stateNormal {
return nil
}
c.state = stateClosed
return nil
}
func (c *client) getReq() *http.Request {
req := c.req
url := c.url
req.URL = &url
query := req.URL.Query()
query.Set("t", fmt.Sprintf("%d-%d", time.Now().Unix()*1000, c.seq))
c.seq++
req.URL.RawQuery = query.Encode()
return &req
}
func (c *client) doPost() error {
if c.state != stateNormal {
return io.EOF
}
req := c.getReq()
req.Method = "POST"
buf := bytes.NewBuffer(nil)
if err := c.payloadEncoder.EncodeTo(buf); err != nil {
return err
}
req.Body = ioutil.NopCloser(buf)
var err error
c.postResp, err = c.client.Do(req)
if err != nil {
return err
}
if c.resp == nil {
c.resp = c.postResp
}
return nil
}
type clientWriter struct {
io.WriteCloser
client *client
}
func newClientWriter(c *client, w io.WriteCloser) io.WriteCloser {
return &clientWriter{
WriteCloser: w,
client: c,
}
}
func (w *clientWriter) Close() error {
if err := w.WriteCloser.Close(); err != nil {
return err
}
return w.client.doPost()
}

View File

@ -0,0 +1,231 @@
package polling
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
. "github.com/smartystreets/goconvey/convey"
)
func TestPolling(t *testing.T) {
Convey("Normal", t, func() {
s := newServer()
server := httptest.NewServer(s)
defer server.Close()
req, err := http.NewRequest("GET", server.URL, nil)
So(err, ShouldBeNil)
client, err := NewClient(req)
So(err, ShouldBeNil)
So(client.Response(), ShouldBeNil)
sync := make(chan int)
go func() {
<-s.callback.onPacket
sync <- 1
}()
{
w, err := client.NextWriter(message.MessageBinary, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("123"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
{
<-sync
So(s.callback.messageType, ShouldEqual, message.MessageBinary)
So(s.callback.packetType, ShouldEqual, parser.MESSAGE)
So(s.callback.body, ShouldResemble, []byte("123"))
}
So(client.Response(), ShouldNotBeNil)
So(client.Response().StatusCode, ShouldEqual, http.StatusOK)
So(client.Response().Header.Get("Custom"), ShouldEqual, "value")
{
w, err := s.server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("abc1"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
w, err = s.server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("abc2"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
{
r, err := client.NextReader()
So(err, ShouldBeNil)
b, err := ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(b, ShouldResemble, []byte("abc1"))
err = r.Close()
So(err, ShouldBeNil)
r, err = client.NextReader()
So(err, ShouldBeNil)
b, err = ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(b, ShouldResemble, []byte("abc2"))
err = r.Close()
So(err, ShouldBeNil)
}
{
w, err := s.server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("abc"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
{
r, err := client.NextReader()
So(err, ShouldBeNil)
b, err := ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(b, ShouldResemble, []byte("abc"))
err = r.Close()
So(err, ShouldBeNil)
}
client.Close()
})
Convey("Normal b64", t, func() {
s := newServer()
server := httptest.NewServer(s)
defer server.Close()
req, err := http.NewRequest("GET", server.URL+"?b64", nil)
So(err, ShouldBeNil)
client, err := NewClient(req)
So(err, ShouldBeNil)
So(client.Response(), ShouldBeNil)
sync := make(chan int)
go func() {
<-s.callback.onPacket
sync <- 1
}()
{
w, err := client.NextWriter(message.MessageBinary, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("123"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
{
<-sync
So(s.callback.messageType, ShouldEqual, message.MessageBinary)
So(s.callback.packetType, ShouldEqual, parser.MESSAGE)
So(s.callback.body, ShouldResemble, []byte("123"))
}
So(client.Response(), ShouldNotBeNil)
So(client.Response().StatusCode, ShouldEqual, http.StatusOK)
So(client.Response().Header.Get("Custom"), ShouldEqual, "value")
{
w, err := s.server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("abc1"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
w, err = s.server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("abc2"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
{
r, err := client.NextReader()
So(err, ShouldBeNil)
b, err := ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(b, ShouldResemble, []byte("abc1"))
err = r.Close()
So(err, ShouldBeNil)
r, err = client.NextReader()
So(err, ShouldBeNil)
b, err = ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(b, ShouldResemble, []byte("abc2"))
err = r.Close()
So(err, ShouldBeNil)
}
{
w, err := s.server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("abc"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
{
r, err := client.NextReader()
So(err, ShouldBeNil)
b, err := ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(b, ShouldResemble, []byte("abc"))
err = r.Close()
So(err, ShouldBeNil)
}
client.Close()
})
}
type server struct {
server transport.Server
callback *fakeCallback
}
func newServer() *server {
return &server{
callback: newFakeCallback(),
}
}
func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if s.server == nil {
var err error
s.server, err = NewServer(w, r, s.callback)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
w.Header().Set("Custom", "value")
s.server.ServeHTTP(w, r)
}

View File

@ -0,0 +1,197 @@
package polling
import (
"bytes"
"html/template"
"io"
"net/http"
"sync"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
)
type state int
const (
stateUnknow state = iota
stateNormal
stateClosing
stateClosed
)
type Polling struct {
sendChan chan bool
encoder *parser.PayloadEncoder
callback transport.Callback
getLocker *Locker
postLocker *Locker
state state
stateLocker sync.Mutex
}
func NewServer(w http.ResponseWriter, r *http.Request, callback transport.Callback) (transport.Server, error) {
newEncoder := parser.NewBinaryPayloadEncoder
if r.URL.Query()["b64"] != nil {
newEncoder = parser.NewStringPayloadEncoder
}
ret := &Polling{
sendChan: MakeSendChan(),
encoder: newEncoder(),
callback: callback,
getLocker: NewLocker(),
postLocker: NewLocker(),
state: stateNormal,
}
return ret, nil
}
func (p *Polling) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
p.get(w, r)
case "POST":
p.post(w, r)
}
}
func (p *Polling) Close() error {
if p.getState() != stateNormal {
return nil
}
close(p.sendChan)
p.setState(stateClosing)
if p.getLocker.TryLock() {
if p.postLocker.TryLock() {
p.callback.OnClose(p)
p.setState(stateClosed)
p.postLocker.Unlock()
}
p.getLocker.Unlock()
}
return nil
}
func (p *Polling) NextWriter(msgType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
if p.getState() != stateNormal {
return nil, io.EOF
}
var ret io.WriteCloser
var err error
switch msgType {
case message.MessageText:
ret, err = p.encoder.NextString(packetType)
case message.MessageBinary:
ret, err = p.encoder.NextBinary(packetType)
}
if err != nil {
return nil, err
}
return NewWriter(ret, p), nil
}
func (p *Polling) get(w http.ResponseWriter, r *http.Request) {
if !p.getLocker.TryLock() {
http.Error(w, "overlay get", http.StatusBadRequest)
return
}
if p.getState() != stateNormal {
http.Error(w, "closed", http.StatusBadRequest)
return
}
defer func() {
if p.getState() == stateClosing {
if p.postLocker.TryLock() {
p.setState(stateClosed)
p.callback.OnClose(p)
p.postLocker.Unlock()
}
}
p.getLocker.Unlock()
}()
<-p.sendChan
if j := r.URL.Query().Get("j"); j != "" {
// JSONP Polling
w.Header().Set("Content-Type", "text/javascript; charset=UTF-8")
tmp := bytes.Buffer{}
p.encoder.EncodeTo(&tmp)
pl := template.JSEscapeString(tmp.String())
w.Write([]byte("___eio[" + j + "](\""))
w.Write([]byte(pl))
w.Write([]byte("\");"))
} else {
// XHR Polling
if p.encoder.IsString() {
w.Header().Set("Content-Type", "text/plain; charset=UTF-8")
} else {
w.Header().Set("Content-Type", "application/octet-stream")
}
p.encoder.EncodeTo(w)
}
}
func (p *Polling) post(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
if !p.postLocker.TryLock() {
http.Error(w, "overlay post", http.StatusBadRequest)
return
}
if p.getState() != stateNormal {
http.Error(w, "closed", http.StatusBadRequest)
return
}
defer func() {
if p.getState() == stateClosing {
if p.getLocker.TryLock() {
p.setState(stateClosed)
p.callback.OnClose(p)
p.getLocker.Unlock()
}
}
p.postLocker.Unlock()
}()
var decoder *parser.PayloadDecoder
if j := r.URL.Query().Get("j"); j != "" {
// JSONP Polling
d := r.FormValue("d")
decoder = parser.NewPayloadDecoder(bytes.NewBufferString(d))
} else {
// XHR Polling
decoder = parser.NewPayloadDecoder(r.Body)
}
for {
d, err := decoder.Next()
if err == io.EOF {
break
}
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
p.callback.OnPacket(d)
d.Close()
}
w.Write([]byte("ok"))
}
func (p *Polling) setState(s state) {
p.stateLocker.Lock()
defer p.stateLocker.Unlock()
p.state = s
}
func (p *Polling) getState() state {
p.stateLocker.Lock()
defer p.stateLocker.Unlock()
return p.state
}

View File

@ -0,0 +1,507 @@
package polling
import (
"bytes"
"encoding/hex"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
. "github.com/smartystreets/goconvey/convey"
)
func TestServer(t *testing.T) {
Convey("Test polling", t, func() {
Convey("Overlay get", func() {
sync := make(chan int)
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
server.ServeHTTP(w, r)
sync <- 1
}()
time.Sleep(time.Second)
{
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusBadRequest)
So(w.Body.String(), ShouldEqual, "overlay get\n")
}
server.Close()
<-sync
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("Overlay post", func() {
sync := make(chan int)
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
server.ServeHTTP(w, r)
sync <- 1
}()
time.Sleep(time.Second)
{
w := httptest.NewRecorder()
r, err := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusBadRequest)
So(w.Body.String(), ShouldEqual, "overlay post\n")
}
<-f.onPacket
server.Close()
<-sync
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("Get", func() {
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
{
writer, err := server.NextWriter(message.MessageBinary, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = writer.Write([]byte("测试"))
So(err, ShouldBeNil)
err = writer.Close()
So(err, ShouldBeNil)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusOK)
So(w.Header().Get("Content-Type"), ShouldEqual, "application/octet-stream")
So(hex.EncodeToString(w.Body.Bytes()), ShouldEqual, "0107ff04e6b58be8af95")
So(w.Body.String(), ShouldEqual, "\x01\x07\xff\x04测试")
}
{
writer, err := server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = writer.Write([]byte("测试"))
So(err, ShouldBeNil)
err = writer.Close()
So(err, ShouldBeNil)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusOK)
So(w.Header().Get("Content-Type"), ShouldEqual, "application/octet-stream")
So(hex.EncodeToString(w.Body.Bytes()), ShouldEqual, "0007ff34e6b58be8af95")
So(w.Body.String(), ShouldEqual, "\x00\x07\xff4测试")
}
err = server.Close()
So(err, ShouldBeNil)
{
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusBadRequest)
So(w.Body.String(), ShouldEqual, "closed\n")
}
{
writer, err := server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldEqual, io.EOF)
So(writer, ShouldBeNil)
}
})
Convey("Get b64", func() {
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/?b64", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
{
writer, err := server.NextWriter(message.MessageBinary, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = writer.Write([]byte("测试"))
So(err, ShouldBeNil)
err = writer.Close()
So(err, ShouldBeNil)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusOK)
So(w.Header().Get("Content-Type"), ShouldEqual, "text/plain; charset=UTF-8")
So(w.Body.String(), ShouldEqual, "10:b45rWL6K+V")
}
{
writer, err := server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = writer.Write([]byte("测试"))
So(err, ShouldBeNil)
err = writer.Close()
So(err, ShouldBeNil)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusOK)
So(w.Header().Get("Content-Type"), ShouldEqual, "text/plain; charset=UTF-8")
So(w.Body.String(), ShouldEqual, "7:4测试")
}
err = server.Close()
So(err, ShouldBeNil)
{
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusBadRequest)
So(w.Body.String(), ShouldEqual, "closed\n")
}
{
writer, err := server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldEqual, io.EOF)
So(writer, ShouldBeNil)
}
})
Convey("Post", func() {
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
<-f.onPacket
}()
{
w := httptest.NewRecorder()
r, err := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusOK)
So(w.Body.String(), ShouldEqual, "ok")
So(hex.EncodeToString(f.body), ShouldEqual, "e6b58be8af95")
}
{
w := httptest.NewRecorder()
r, err := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\xff4测试"))
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusBadRequest)
So(w.Body.String(), ShouldEqual, "invalid input\n")
}
err = server.Close()
So(err, ShouldBeNil)
{
w := httptest.NewRecorder()
r, err := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
So(err, ShouldBeNil)
server.ServeHTTP(w, r)
So(w.Code, ShouldEqual, http.StatusBadRequest)
So(w.Body.String(), ShouldEqual, "closed\n")
}
})
Convey("Closing", func() {
Convey("No get no post", func() {
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
So(f.ClosedCount(), ShouldEqual, 0)
err = server.Close()
So(err, ShouldBeNil)
So(f.ClosedCount(), ShouldEqual, 1)
So(f.closeServer, ShouldEqual, server)
})
Convey("No get has post", func() {
f := newFakeCallback()
sync := make(chan int)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
server.ServeHTTP(w, r)
sync <- 1
}()
time.Sleep(time.Second)
So(f.ClosedCount(), ShouldEqual, 0)
err = server.Close()
So(err, ShouldBeNil)
So(f.ClosedCount(), ShouldEqual, 0)
<-f.onPacket
<-sync
So(f.closeServer, ShouldEqual, server)
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("has get no post", func() {
f := newFakeCallback()
sync := make(chan int)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
server.ServeHTTP(w, r)
sync <- 1
}()
time.Sleep(time.Second)
So(f.ClosedCount(), ShouldEqual, 0)
err = server.Close()
So(err, ShouldBeNil)
<-sync
So(f.closeServer, ShouldEqual, server)
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("has get has post", func() {
f := newFakeCallback()
sync := make(chan int)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
server.ServeHTTP(w, r)
sync <- 1
}()
go func() {
w := httptest.NewRecorder()
r, _ = http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
server.ServeHTTP(w, r)
sync <- 1
}()
time.Sleep(time.Second)
So(f.ClosedCount(), ShouldEqual, 0)
err = server.Close()
So(err, ShouldBeNil)
So(f.ClosedCount(), ShouldEqual, 0)
<-f.onPacket
<-sync
<-sync
So(f.closeServer, ShouldEqual, server)
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("Multi-close", func() {
f := newFakeCallback()
sync := make(chan int)
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("GET", "/", nil)
server.ServeHTTP(w, r)
sync <- 1
}()
go func() {
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "/", bytes.NewBufferString("\x00\x07\xff4测试"))
server.ServeHTTP(w, r)
sync <- 1
}()
time.Sleep(time.Second)
So(f.ClosedCount(), ShouldEqual, 0)
err = server.Close()
So(err, ShouldBeNil)
So(f.ClosedCount(), ShouldEqual, 0)
<-f.onPacket
<-sync
<-sync
So(f.closeServer, ShouldEqual, server)
server.Close()
server.Close()
server.Close()
server.Close()
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("Closed before writer closed", func() {
f := newFakeCallback()
w := httptest.NewRecorder()
r, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
server, err := NewServer(w, r, f)
So(err, ShouldBeNil)
writer, err := server.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
err = server.Close()
So(err, ShouldBeNil)
err = writer.Close()
So(err, ShouldNotBeNil)
})
})
})
}
type fakeCallback struct {
onPacket chan bool
messageType message.MessageType
packetType parser.PacketType
body []byte
err error
closedCount int
countLocker sync.Mutex
closeServer transport.Server
}
func newFakeCallback() *fakeCallback {
return &fakeCallback{
onPacket: make(chan bool),
}
}
func (f *fakeCallback) OnPacket(r *parser.PacketDecoder) {
f.packetType = r.Type()
f.messageType = r.MessageType()
f.body, f.err = ioutil.ReadAll(r)
f.onPacket <- true
}
func (f *fakeCallback) OnClose(s transport.Server) {
f.countLocker.Lock()
defer f.countLocker.Unlock()
f.closedCount++
f.closeServer = s
}
func (f *fakeCallback) ClosedCount() int {
f.countLocker.Lock()
defer f.countLocker.Unlock()
return f.closedCount
}

View File

@ -0,0 +1,28 @@
package polling
type Locker struct {
locker chan struct{}
}
func NewLocker() *Locker {
return &Locker{
locker: make(chan struct{}, 1),
}
}
func (l *Locker) Lock() {
l.locker <- struct{}{}
}
func (l *Locker) TryLock() bool {
select {
case l.locker <- struct{}{}:
return true
default:
return false
}
}
func (l *Locker) Unlock() {
<-l.locker
}

View File

@ -0,0 +1,47 @@
package polling
import (
"testing"
"time"
. "github.com/smartystreets/goconvey/convey"
)
func TestTryLocker(t *testing.T) {
Convey("Lock/Unlock", t, func() {
locker := NewLocker()
sync := make(chan int)
go func() {
locker.Lock()
sync <- 1
time.Sleep(time.Second)
locker.Unlock()
}()
<-sync
start := time.Now()
locker.Lock()
now := time.Now()
So(now.Sub(start), ShouldBeGreaterThanOrEqualTo, time.Second)
locker.Unlock()
})
Convey("TryLock/Unlock", t, func() {
locker := NewLocker()
sync := make(chan int)
go func() {
locker.Lock()
sync <- 1
time.Sleep(time.Second)
locker.Unlock()
}()
<-sync
ok := locker.TryLock()
So(ok, ShouldBeFalse)
time.Sleep(time.Second * 3 / 2)
ok = locker.TryLock()
So(ok, ShouldBeTrue)
locker.Unlock()
})
}

View File

@ -0,0 +1,33 @@
package polling
import (
"errors"
"io"
)
func MakeSendChan() chan bool {
return make(chan bool, 1)
}
type Writer struct {
io.WriteCloser
server *Polling
}
func NewWriter(w io.WriteCloser, server *Polling) *Writer {
return &Writer{
WriteCloser: w,
server: server,
}
}
func (w *Writer) Close() error {
if w.server.getState() != stateNormal {
return errors.New("use of closed network connection")
}
select {
case w.server.sendChan <- true:
default:
}
return w.WriteCloser.Close()
}

View File

@ -0,0 +1,89 @@
package polling
import (
"bytes"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestWriter(t *testing.T) {
p := &Polling{
state: stateNormal,
sendChan: MakeSendChan(),
}
sendChan := p.sendChan
Convey("Wait close", t, func() {
w := newFakeWriteCloser()
select {
case <-sendChan:
panic("should not run here")
default:
}
writer := NewWriter(w, p)
err := writer.Close()
So(err, ShouldBeNil)
select {
case <-sendChan:
default:
panic("should not run here")
}
select {
case <-sendChan:
panic("should not run here")
default:
}
})
Convey("Many writer with close", t, func() {
for i := 0; i < 10; i++ {
w := newFakeWriteCloser()
writer := NewWriter(w, p)
err := writer.Close()
So(err, ShouldBeNil)
}
select {
case <-sendChan:
default:
panic("should not run here")
}
select {
case <-sendChan:
panic("should not run here")
default:
}
})
Convey("Close with not normal", t, func() {
p := &Polling{
state: stateClosing,
sendChan: MakeSendChan(),
}
w := newFakeWriteCloser()
writer := NewWriter(w, p)
err := writer.Close()
So(err, ShouldNotBeNil)
})
}
type fakeWriteCloser struct {
*bytes.Buffer
}
func newFakeWriteCloser() *fakeWriteCloser {
return &fakeWriteCloser{
Buffer: bytes.NewBuffer(nil),
}
}
func (f *fakeWriteCloser) Close() error {
return nil
}

View File

@ -0,0 +1,12 @@
package polling
import (
"github.com/googollee/go-engine.io/transport"
)
var Creater = transport.Creater{
Name: "polling",
Upgrading: false,
Server: NewServer,
Client: NewClient,
}

188
vendor/github.com/googollee/go-engine.io/server.go generated vendored Normal file
View File

@ -0,0 +1,188 @@
package engineio
import (
"bytes"
"crypto/md5"
"encoding/base64"
"fmt"
"net/http"
"sync/atomic"
"time"
"github.com/googollee/go-engine.io/polling"
"github.com/googollee/go-engine.io/websocket"
)
type config struct {
PingTimeout time.Duration
PingInterval time.Duration
MaxConnection int
AllowRequest func(*http.Request) error
AllowUpgrades bool
Cookie string
NewId func(r *http.Request) string
}
// Server is the server of engine.io.
type Server struct {
config config
socketChan chan Conn
serverSessions Sessions
creaters transportCreaters
currentConnection int32
}
// NewServer returns the server suppported given transports. If transports is nil, server will use ["polling", "websocket"] as default.
func NewServer(transports []string) (*Server, error) {
if transports == nil {
transports = []string{"polling", "websocket"}
}
creaters := make(transportCreaters)
for _, t := range transports {
switch t {
case "polling":
creaters[t] = polling.Creater
case "websocket":
creaters[t] = websocket.Creater
default:
return nil, InvalidError
}
}
return &Server{
config: config{
PingTimeout: 60000 * time.Millisecond,
PingInterval: 25000 * time.Millisecond,
MaxConnection: 1000,
AllowRequest: func(*http.Request) error { return nil },
AllowUpgrades: true,
Cookie: "io",
NewId: newId,
},
socketChan: make(chan Conn),
serverSessions: newServerSessions(),
creaters: creaters,
}, nil
}
// SetPingTimeout sets the timeout of ping. When time out, server will close connection. Default is 60s.
func (s *Server) SetPingTimeout(t time.Duration) {
s.config.PingTimeout = t
}
// SetPingInterval sets the interval of ping. Default is 25s.
func (s *Server) SetPingInterval(t time.Duration) {
s.config.PingInterval = t
}
// SetMaxConnection sets the max connetion. Default is 1000.
func (s *Server) SetMaxConnection(n int) {
s.config.MaxConnection = n
}
// GetMaxConnection returns the current max connection
func (s *Server) GetMaxConnection() int {
return s.config.MaxConnection
}
// Count returns a count of current number of active connections in session
func (s *Server) Count() int {
return int(atomic.LoadInt32(&s.currentConnection))
}
// SetAllowRequest sets the middleware function when establish connection. If it return non-nil, connection won't be established. Default will allow all request.
func (s *Server) SetAllowRequest(f func(*http.Request) error) {
s.config.AllowRequest = f
}
// SetAllowUpgrades sets whether server allows transport upgrade. Default is true.
func (s *Server) SetAllowUpgrades(allow bool) {
s.config.AllowUpgrades = allow
}
// SetCookie sets the name of cookie which used by engine.io. Default is "io".
func (s *Server) SetCookie(prefix string) {
s.config.Cookie = prefix
}
// SetNewId sets the callback func to generate new connection id. By default, id is generated from remote addr + current time stamp
func (s *Server) SetNewId(f func(*http.Request) string) {
s.config.NewId = f
}
// SetSessionManager sets the sessions as server's session manager. Default sessions is single process manager. You can custom it as load balance.
func (s *Server) SetSessionManager(sessions Sessions) {
s.serverSessions = sessions
}
// ServeHTTP handles http request.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
sid := r.URL.Query().Get("sid")
conn := s.serverSessions.Get(sid)
if conn == nil {
if sid != "" {
http.Error(w, "invalid sid", http.StatusBadRequest)
return
}
if err := s.config.AllowRequest(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
n := atomic.AddInt32(&s.currentConnection, 1)
if int(n) > s.config.MaxConnection {
atomic.AddInt32(&s.currentConnection, -1)
http.Error(w, "too many connections", http.StatusServiceUnavailable)
return
}
sid = s.config.NewId(r)
var err error
conn, err = newServerConn(sid, w, r, s)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
s.serverSessions.Set(sid, conn)
s.socketChan <- conn
}
http.SetCookie(w, &http.Cookie{
Name: s.config.Cookie,
Value: sid,
})
conn.(*serverConn).ServeHTTP(w, r)
}
// Accept returns Conn when client connect to server.
func (s *Server) Accept() (Conn, error) {
return <-s.socketChan, nil
}
func (s *Server) configure() config {
return s.config
}
func (s *Server) transports() transportCreaters {
return s.creaters
}
func (s *Server) onClose(id string) {
s.serverSessions.Remove(id)
atomic.AddInt32(&s.currentConnection, -1)
}
func newId(r *http.Request) string {
hash := fmt.Sprintf("%s %s", r.RemoteAddr, time.Now())
buf := bytes.NewBuffer(nil)
sum := md5.Sum([]byte(hash))
encoder := base64.NewEncoder(base64.URLEncoding, buf)
encoder.Write(sum[:])
encoder.Close()
return buf.String()[:20]
}

388
vendor/github.com/googollee/go-engine.io/server_conn.go generated vendored Normal file
View File

@ -0,0 +1,388 @@
package engineio
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"sync"
"time"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
)
type MessageType message.MessageType
const (
MessageBinary MessageType = MessageType(message.MessageBinary)
MessageText MessageType = MessageType(message.MessageText)
)
// Conn is the connection object of engine.io.
type Conn interface {
// Id returns the session id of connection.
Id() string
// Request returns the first http request when established connection.
Request() *http.Request
// Close closes the connection.
Close() error
// NextReader returns the next message type, reader. If no message received, it will block.
NextReader() (MessageType, io.ReadCloser, error)
// NextWriter returns the next message writer with given message type.
NextWriter(messageType MessageType) (io.WriteCloser, error)
}
type transportCreaters map[string]transport.Creater
func (c transportCreaters) Get(name string) transport.Creater {
return c[name]
}
type serverCallback interface {
configure() config
transports() transportCreaters
onClose(sid string)
}
type state int
const (
stateUnknow state = iota
stateNormal
stateUpgrading
stateClosing
stateClosed
)
type serverConn struct {
id string
request *http.Request
callback serverCallback
writerLocker sync.Mutex
transportLocker sync.RWMutex
currentName string
current transport.Server
upgradingName string
upgrading transport.Server
state state
stateLocker sync.RWMutex
readerChan chan *connReader
pingTimeout time.Duration
pingInterval time.Duration
pingChan chan bool
pingLocker sync.Mutex
}
var InvalidError = errors.New("invalid transport")
func newServerConn(id string, w http.ResponseWriter, r *http.Request, callback serverCallback) (*serverConn, error) {
transportName := r.URL.Query().Get("transport")
creater := callback.transports().Get(transportName)
if creater.Name == "" {
return nil, InvalidError
}
ret := &serverConn{
id: id,
request: r,
callback: callback,
state: stateNormal,
readerChan: make(chan *connReader),
pingTimeout: callback.configure().PingTimeout,
pingInterval: callback.configure().PingInterval,
pingChan: make(chan bool),
}
transport, err := creater.Server(w, r, ret)
if err != nil {
return nil, err
}
ret.setCurrent(transportName, transport)
if err := ret.onOpen(); err != nil {
return nil, err
}
go ret.pingLoop()
return ret, nil
}
func (c *serverConn) Id() string {
return c.id
}
func (c *serverConn) Request() *http.Request {
return c.request
}
func (c *serverConn) NextReader() (MessageType, io.ReadCloser, error) {
if c.getState() == stateClosed {
return MessageBinary, nil, io.EOF
}
ret := <-c.readerChan
if ret == nil {
return MessageBinary, nil, io.EOF
}
return MessageType(ret.MessageType()), ret, nil
}
func (c *serverConn) NextWriter(t MessageType) (io.WriteCloser, error) {
switch c.getState() {
case stateUpgrading:
for i := 0; i < 30; i++ {
time.Sleep(50 * time.Millisecond)
if c.getState() != stateUpgrading {
break
}
}
if c.getState() == stateUpgrading {
return nil, fmt.Errorf("upgrading")
}
case stateNormal:
default:
return nil, io.EOF
}
c.writerLocker.Lock()
ret, err := c.getCurrent().NextWriter(message.MessageType(t), parser.MESSAGE)
if err != nil {
c.writerLocker.Unlock()
return ret, err
}
writer := newConnWriter(ret, &c.writerLocker)
return writer, err
}
func (c *serverConn) Close() error {
if c.getState() != stateNormal && c.getState() != stateUpgrading {
return nil
}
if c.upgrading != nil {
c.upgrading.Close()
}
c.writerLocker.Lock()
if w, err := c.getCurrent().NextWriter(message.MessageText, parser.CLOSE); err == nil {
writer := newConnWriter(w, &c.writerLocker)
writer.Close()
} else {
c.writerLocker.Unlock()
}
if err := c.getCurrent().Close(); err != nil {
return err
}
c.setState(stateClosing)
return nil
}
func (c *serverConn) ServeHTTP(w http.ResponseWriter, r *http.Request) {
transportName := r.URL.Query().Get("transport")
if c.currentName != transportName {
creater := c.callback.transports().Get(transportName)
if creater.Name == "" {
http.Error(w, fmt.Sprintf("invalid transport %s", transportName), http.StatusBadRequest)
return
}
u, err := creater.Server(w, r, c)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
c.setUpgrading(creater.Name, u)
return
}
c.current.ServeHTTP(w, r)
}
func (c *serverConn) OnPacket(r *parser.PacketDecoder) {
if s := c.getState(); s != stateNormal && s != stateUpgrading {
return
}
switch r.Type() {
case parser.OPEN:
case parser.CLOSE:
c.getCurrent().Close()
case parser.PING:
c.writerLocker.Lock()
t := c.getCurrent()
u := c.getUpgrade()
newWriter := t.NextWriter
if u != nil {
if w, _ := t.NextWriter(message.MessageText, parser.NOOP); w != nil {
w.Close()
}
newWriter = u.NextWriter
}
if w, _ := newWriter(message.MessageText, parser.PONG); w != nil {
io.Copy(w, r)
w.Close()
}
c.writerLocker.Unlock()
fallthrough
case parser.PONG:
c.pingLocker.Lock()
defer c.pingLocker.Unlock()
if s := c.getState(); s != stateNormal && s != stateUpgrading {
return
}
c.pingChan <- true
case parser.MESSAGE:
closeChan := make(chan struct{})
c.readerChan <- newConnReader(r, closeChan)
<-closeChan
close(closeChan)
r.Close()
case parser.UPGRADE:
c.upgraded()
case parser.NOOP:
}
}
func (c *serverConn) OnClose(server transport.Server) {
if t := c.getUpgrade(); server == t {
c.setUpgrading("", nil)
t.Close()
return
}
t := c.getCurrent()
if server != t {
return
}
t.Close()
if t := c.getUpgrade(); t != nil {
t.Close()
c.setUpgrading("", nil)
}
c.setState(stateClosed)
close(c.readerChan)
c.pingLocker.Lock()
close(c.pingChan)
c.pingLocker.Unlock()
c.callback.onClose(c.id)
}
func (s *serverConn) onOpen() error {
upgrades := []string{}
for name := range s.callback.transports() {
if name == s.currentName {
continue
}
upgrades = append(upgrades, name)
}
type connectionInfo struct {
Sid string `json:"sid"`
Upgrades []string `json:"upgrades"`
PingInterval time.Duration `json:"pingInterval"`
PingTimeout time.Duration `json:"pingTimeout"`
}
resp := connectionInfo{
Sid: s.Id(),
Upgrades: upgrades,
PingInterval: s.callback.configure().PingInterval / time.Millisecond,
PingTimeout: s.callback.configure().PingTimeout / time.Millisecond,
}
w, err := s.getCurrent().NextWriter(message.MessageText, parser.OPEN)
if err != nil {
return err
}
encoder := json.NewEncoder(w)
if err := encoder.Encode(resp); err != nil {
return err
}
if err := w.Close(); err != nil {
return err
}
return nil
}
func (c *serverConn) getCurrent() transport.Server {
c.transportLocker.RLock()
defer c.transportLocker.RUnlock()
return c.current
}
func (c *serverConn) getUpgrade() transport.Server {
c.transportLocker.RLock()
defer c.transportLocker.RUnlock()
return c.upgrading
}
func (c *serverConn) setCurrent(name string, s transport.Server) {
c.transportLocker.Lock()
defer c.transportLocker.Unlock()
c.currentName = name
c.current = s
}
func (c *serverConn) setUpgrading(name string, s transport.Server) {
c.transportLocker.Lock()
defer c.transportLocker.Unlock()
c.upgradingName = name
c.upgrading = s
c.setState(stateUpgrading)
}
func (c *serverConn) upgraded() {
c.transportLocker.Lock()
current := c.current
c.current = c.upgrading
c.currentName = c.upgradingName
c.upgrading = nil
c.upgradingName = ""
c.transportLocker.Unlock()
current.Close()
c.setState(stateNormal)
}
func (c *serverConn) getState() state {
c.stateLocker.RLock()
defer c.stateLocker.RUnlock()
return c.state
}
func (c *serverConn) setState(state state) {
c.stateLocker.Lock()
defer c.stateLocker.Unlock()
c.state = state
}
func (c *serverConn) pingLoop() {
lastPing := time.Now()
lastTry := lastPing
for {
now := time.Now()
pingDiff := now.Sub(lastPing)
tryDiff := now.Sub(lastTry)
select {
case ok := <-c.pingChan:
if !ok {
return
}
lastPing = time.Now()
lastTry = lastPing
case <-time.After(c.pingInterval - tryDiff):
c.writerLocker.Lock()
if w, _ := c.getCurrent().NextWriter(message.MessageText, parser.PING); w != nil {
writer := newConnWriter(w, &c.writerLocker)
writer.Close()
} else {
c.writerLocker.Unlock()
}
lastTry = time.Now()
case <-time.After(c.pingTimeout - pingDiff):
c.Close()
return
}
}
}

View File

@ -0,0 +1,377 @@
package engineio
import (
"net/http"
"net/http/httptest"
"net/url"
"sync"
"testing"
"time"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/polling"
"github.com/googollee/go-engine.io/websocket"
. "github.com/smartystreets/goconvey/convey"
)
type FakeServer struct {
config *config
creaters transportCreaters
closed map[string]int
closedLocker sync.Mutex
}
func newFakeServer() *FakeServer {
return &FakeServer{
config: &config{
PingTimeout: time.Second * 2,
PingInterval: time.Second * 1,
AllowUpgrades: true,
},
creaters: transportCreaters{
"polling": polling.Creater,
"websocket": websocket.Creater,
},
closed: make(map[string]int),
}
}
func (f *FakeServer) configure() config {
return *f.config
}
func (f *FakeServer) transports() transportCreaters {
return f.creaters
}
func (f *FakeServer) onClose(sid string) {
f.closedLocker.Lock()
defer f.closedLocker.Unlock()
f.closed[sid] = f.closed[sid] + 1
}
func TestConn(t *testing.T) {
Convey("Create conn", t, func() {
Convey("without transport", func() {
server := newFakeServer()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
resp := httptest.NewRecorder()
_, err = newServerConn("id", resp, req, server)
So(err, ShouldEqual, InvalidError)
})
Convey("with invalid transport", func() {
server := newFakeServer()
req, err := http.NewRequest("GET", "/?transport=websocket", nil)
So(err, ShouldBeNil)
resp := httptest.NewRecorder()
_, err = newServerConn("id", resp, req, server)
So(err, ShouldNotBeNil)
})
Convey("ok", func() {
Convey("with polling", func() {
server := newFakeServer()
req, err := http.NewRequest("GET", "/?transport=polling", nil)
So(err, ShouldBeNil)
resp := httptest.NewRecorder()
conn, err := newServerConn("id", resp, req, server)
So(err, ShouldBeNil)
So(conn.Id(), ShouldEqual, "id")
So(conn.Request(), ShouldEqual, req)
conn.Close()
})
Convey("with websocket", func() {
server := newFakeServer()
h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, err := newServerConn("id", w, r, server)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
if conn.Id() != "id" {
t.Fatal(err)
}
if conn.Request() != r {
t.Fatal(err)
}
}))
defer h.Close()
u, _ := url.Parse(h.URL)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String()+"/?transport=websocket", nil)
So(err, ShouldBeNil)
So(req, ShouldNotBeNil)
c, err := websocket.NewClient(req)
So(err, ShouldBeNil)
defer c.Close()
})
})
})
Convey("Upgrade conn", t, func() {
Convey("polling to websocket", func() {
server := newFakeServer()
id := "id"
var conn *serverConn
h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if conn == nil {
var err error
conn, err = newServerConn(id, w, r, server)
if err != nil {
t.Fatal(err)
}
}
conn.ServeHTTP(w, r)
}))
defer h.Close()
u, err := url.Parse(h.URL)
So(err, ShouldBeNil)
req, err := http.NewRequest("GET", u.String()+"/?transport=polling", nil)
So(err, ShouldBeNil)
pc, err := polling.NewClient(req)
So(err, ShouldBeNil)
decoder, err := pc.NextReader()
So(err, ShouldBeNil)
So(pc.Response().StatusCode, ShouldEqual, http.StatusOK)
So(conn, ShouldNotBeNil)
So(conn, ShouldImplement, (*Conn)(nil))
So(decoder.MessageType(), ShouldEqual, message.MessageText)
So(decoder.Type(), ShouldEqual, parser.OPEN)
So(conn.getCurrent(), ShouldNotBeNil)
So(conn.getUpgrade(), ShouldBeNil)
u.Scheme = "ws"
req, err = http.NewRequest("GET", u.String()+"/?transport=websocket", nil)
So(err, ShouldBeNil)
wc, err := websocket.NewClient(req)
So(err, ShouldBeNil)
So(conn.getCurrent(), ShouldNotBeNil)
So(conn.getUpgrade(), ShouldNotBeNil)
encoder, err := wc.NextWriter(message.MessageBinary, parser.PING)
So(err, ShouldBeNil)
encoder.Write([]byte("probe"))
encoder.Close()
decoder, err = wc.NextReader()
So(err, ShouldBeNil)
So(wc.Response().StatusCode, ShouldEqual, http.StatusSwitchingProtocols)
So(decoder.MessageType(), ShouldEqual, message.MessageText)
So(decoder.Type(), ShouldEqual, parser.PONG)
pc.Close()
encoder, err = wc.NextWriter(message.MessageBinary, parser.UPGRADE)
So(err, ShouldBeNil)
encoder.Close()
decoder, err = wc.NextReader()
So(err, ShouldBeNil)
So(pc.Response().StatusCode, ShouldEqual, http.StatusOK)
So(decoder.MessageType(), ShouldEqual, message.MessageText)
So(decoder.Type(), ShouldEqual, parser.PING)
So(conn.getCurrent(), ShouldNotBeNil)
So(conn.getUpgrade(), ShouldBeNil)
wc.Close()
conn.Close()
time.Sleep(time.Second)
server.closedLocker.Lock()
So(server.closed[id], ShouldEqual, 1)
server.closedLocker.Unlock()
err = conn.Close()
So(err, ShouldBeNil)
time.Sleep(time.Second)
server.closedLocker.Lock()
So(server.closed[id], ShouldEqual, 1)
server.closedLocker.Unlock()
})
Convey("close when upgrading", func() {
server := newFakeServer()
id := "id"
var conn *serverConn
h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if conn == nil {
var err error
conn, err = newServerConn(id, w, r, server)
if err != nil {
t.Fatal(err)
}
}
conn.ServeHTTP(w, r)
}))
defer h.Close()
u, err := url.Parse(h.URL)
So(err, ShouldBeNil)
req, err := http.NewRequest("GET", u.String()+"/?transport=polling", nil)
So(err, ShouldBeNil)
pc, err := polling.NewClient(req)
So(err, ShouldBeNil)
decoder, err := pc.NextReader()
So(err, ShouldBeNil)
So(pc.Response().StatusCode, ShouldEqual, http.StatusOK)
So(conn, ShouldNotBeNil)
So(conn, ShouldImplement, (*Conn)(nil))
So(decoder.MessageType(), ShouldEqual, message.MessageText)
So(decoder.Type(), ShouldEqual, parser.OPEN)
So(conn.getCurrent(), ShouldNotBeNil)
So(conn.getUpgrade(), ShouldBeNil)
u.Scheme = "ws"
req, err = http.NewRequest("GET", u.String()+"/?transport=websocket", nil)
So(err, ShouldBeNil)
wc, err := websocket.NewClient(req)
So(err, ShouldBeNil)
So(conn.getCurrent(), ShouldNotBeNil)
So(conn.getUpgrade(), ShouldNotBeNil)
encoder, err := wc.NextWriter(message.MessageBinary, parser.PING)
So(err, ShouldBeNil)
encoder.Write([]byte("probe"))
encoder.Close()
decoder, err = wc.NextReader()
So(err, ShouldBeNil)
So(wc.Response().StatusCode, ShouldEqual, http.StatusSwitchingProtocols)
err = conn.Close()
So(err, ShouldBeNil)
wc.Close()
pc.Close()
time.Sleep(time.Second)
server.closedLocker.Lock()
So(server.closed[id], ShouldEqual, 1)
server.closedLocker.Unlock()
})
})
Convey("Closing", t, func() {
Convey("close timeout by polling", func() {
server := newFakeServer()
id := "id"
var conn *serverConn
h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if conn == nil {
var err error
conn, err = newServerConn(id, w, r, server)
if err != nil {
t.Fatal(err)
}
}
conn.ServeHTTP(w, r)
}))
defer h.Close()
u, err := url.Parse(h.URL)
So(err, ShouldBeNil)
req, err := http.NewRequest("GET", u.String()+"/?transport=polling", nil)
So(err, ShouldBeNil)
pc, err := polling.NewClient(req)
So(err, ShouldBeNil)
decoder, err := pc.NextReader()
So(err, ShouldBeNil)
So(decoder.MessageType(), ShouldEqual, message.MessageText)
So(decoder.Type(), ShouldEqual, parser.OPEN)
pc.Close()
time.Sleep(time.Second * 3)
server.closedLocker.Lock()
So(server.closed[id], ShouldEqual, 1)
server.closedLocker.Unlock()
err = conn.Close()
So(err, ShouldBeNil)
})
Convey("close by websocket", func() {
server := newFakeServer()
id := "id"
var conn *serverConn
var locker sync.Mutex
h := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
locker.Lock()
defer locker.Unlock()
if conn == nil {
var err error
conn, err = newServerConn(id, w, r, server)
if err != nil {
t.Fatal(err)
}
}
conn.ServeHTTP(w, r)
}))
defer h.Close()
u, err := url.Parse(h.URL)
So(err, ShouldBeNil)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String()+"/?transport=websocket", nil)
So(err, ShouldBeNil)
wc, err := websocket.NewClient(req)
So(err, ShouldBeNil)
wc.Close()
time.Sleep(time.Second / 2)
server.closedLocker.Lock()
So(server.closed[id], ShouldEqual, 1)
server.closedLocker.Unlock()
locker.Lock()
err = conn.Close()
locker.Unlock()
So(err, ShouldBeNil)
})
})
}

View File

@ -0,0 +1,92 @@
package engineio
import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/googollee/go-engine.io/parser"
. "github.com/smartystreets/goconvey/convey"
)
func TestServer(t *testing.T) {
Convey("Setup server", t, func() {
server, err := NewServer(nil)
So(err, ShouldBeNil)
server.SetPingInterval(time.Second)
So(server.config.PingInterval, ShouldEqual, time.Second)
server.SetPingTimeout(10 * time.Second)
So(server.config.PingTimeout, ShouldEqual, 10*time.Second)
f := func(*http.Request) error { return nil }
server.SetAllowRequest(f)
So(server.config.AllowRequest, ShouldEqual, f)
server.SetAllowUpgrades(false)
So(server.config.AllowUpgrades, ShouldBeFalse)
server.SetCookie("prefix")
So(server.config.Cookie, ShouldEqual, "prefix")
So(server.GetMaxConnection(), ShouldEqual, 1000)
})
Convey("Create server", t, func() {
Convey("Test new id", func() {
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
id1 := newId(req)
id2 := newId(req)
So(id1, ShouldNotEqual, id2)
})
})
Convey("Max connections", t, func() {
server, _ := NewServer(nil)
server.SetMaxConnection(1)
go func() {
for i := 0; i < 3; i++ {
server.Accept()
}
}()
req1 := newOpenReq()
res1 := httptest.NewRecorder()
server.ServeHTTP(res1, req1)
So(res1.Code, ShouldEqual, 200)
req2 := newOpenReq()
res2 := httptest.NewRecorder()
server.ServeHTTP(res2, req2)
So(res2.Code, ShouldEqual, 503)
So(strings.TrimSpace(string(res2.Body.Bytes())), ShouldEqual, "too many connections")
server.onClose(extractSid(res1.Body))
req3 := newOpenReq()
res3 := httptest.NewRecorder()
server.ServeHTTP(res3, req3)
So(res3.Code, ShouldEqual, 200)
})
}
func newOpenReq() *http.Request {
openReq, _ := http.NewRequest("GET", "/", bytes.NewBuffer([]byte{}))
q := openReq.URL.Query()
q.Set("transport", "polling")
openReq.URL.RawQuery = q.Encode()
return openReq
}
func extractSid(body io.Reader) string {
payload := parser.NewPayloadDecoder(body)
packet, _ := payload.Next()
openRes := map[string]interface{}{}
json.NewDecoder(packet).Decode(&openRes)
return openRes["sid"].(string)
}

47
vendor/github.com/googollee/go-engine.io/sessions.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package engineio
import (
"sync"
)
type Sessions interface {
Get(id string) Conn
Set(id string, conn Conn)
Remove(id string)
}
type serverSessions struct {
sessions map[string]Conn
locker sync.RWMutex
}
func newServerSessions() *serverSessions {
return &serverSessions{
sessions: make(map[string]Conn),
}
}
func (s *serverSessions) Get(id string) Conn {
s.locker.RLock()
defer s.locker.RUnlock()
ret, ok := s.sessions[id]
if !ok {
return nil
}
return ret
}
func (s *serverSessions) Set(id string, conn Conn) {
s.locker.Lock()
defer s.locker.Unlock()
s.sessions[id] = conn
}
func (s *serverSessions) Remove(id string) {
s.locker.Lock()
defer s.locker.Unlock()
delete(s.sessions, id)
}

View File

@ -0,0 +1,25 @@
package engineio
import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)
func TestServerSessions(t *testing.T) {
Convey("Server sessions", t, func() {
sessions := newServerSessions()
So(sessions.Get("a"), ShouldBeNil)
sessions.Set("b", new(serverConn))
So(sessions.Get("b"), ShouldNotBeNil)
So(sessions.Get("a"), ShouldBeNil)
sessions.Set("c", new(serverConn))
So(sessions.Get("c"), ShouldNotBeNil)
sessions.Remove("b")
So(sessions.Get("b"), ShouldBeNil)
})
}

View File

@ -0,0 +1,50 @@
package transport
import (
"io"
"net/http"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
)
type Callback interface {
OnPacket(r *parser.PacketDecoder)
OnClose(server Server)
}
type Creater struct {
Name string
Upgrading bool
Server func(w http.ResponseWriter, r *http.Request, callback Callback) (Server, error)
Client func(r *http.Request) (Client, error)
}
// Server is a transport layer in server to connect client.
type Server interface {
// ServeHTTP handles the http request. It will call conn.onPacket when receive packet.
ServeHTTP(http.ResponseWriter, *http.Request)
// Close closes the transport.
Close() error
// NextWriter returns packet writer. This function call should be synced.
NextWriter(messageType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error)
}
// Client is a transport layer in client to connect server.
type Client interface {
// Response returns the response of last http request.
Response() *http.Response
// NextReader returns packet decoder. This function call should be synced.
NextReader() (*parser.PacketDecoder, error)
// NextWriter returns packet writer. This function call should be synced.
NextWriter(messageType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error)
// Close closes the transport.
Close() error
}

View File

@ -0,0 +1,72 @@
package websocket
import (
"io"
"net/http"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
"github.com/gorilla/websocket"
)
type client struct {
conn *websocket.Conn
resp *http.Response
}
func NewClient(r *http.Request) (transport.Client, error) {
dialer := websocket.DefaultDialer
conn, resp, err := dialer.Dial(r.URL.String(), r.Header)
if err != nil {
return nil, err
}
return &client{
conn: conn,
resp: resp,
}, nil
}
func (c *client) Response() *http.Response {
return c.resp
}
func (c *client) NextReader() (*parser.PacketDecoder, error) {
var reader io.Reader
for {
t, r, err := c.conn.NextReader()
if err != nil {
return nil, err
}
switch t {
case websocket.TextMessage:
fallthrough
case websocket.BinaryMessage:
reader = r
return parser.NewDecoder(reader)
}
}
}
func (c *client) NextWriter(msgType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
wsType, newEncoder := websocket.TextMessage, parser.NewStringEncoder
if msgType == message.MessageBinary {
wsType, newEncoder = websocket.BinaryMessage, parser.NewBinaryEncoder
}
w, err := c.conn.NextWriter(wsType)
if err != nil {
return nil, err
}
ret, err := newEncoder(w, packetType)
if err != nil {
return nil, err
}
return ret, nil
}
func (c *client) Close() error {
return c.conn.Close()
}

View File

@ -0,0 +1,81 @@
package websocket
import (
"io"
"net/http"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
"github.com/googollee/go-engine.io/transport"
"github.com/gorilla/websocket"
)
type Server struct {
callback transport.Callback
conn *websocket.Conn
}
func NewServer(w http.ResponseWriter, r *http.Request, callback transport.Callback) (transport.Server, error) {
conn, err := websocket.Upgrade(w, r, nil, 10240, 10240)
if err != nil {
return nil, err
}
ret := &Server{
callback: callback,
conn: conn,
}
go ret.serveHTTP(w, r)
return ret, nil
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}
func (s *Server) NextWriter(msgType message.MessageType, packetType parser.PacketType) (io.WriteCloser, error) {
wsType, newEncoder := websocket.TextMessage, parser.NewStringEncoder
if msgType == message.MessageBinary {
wsType, newEncoder = websocket.BinaryMessage, parser.NewBinaryEncoder
}
w, err := s.conn.NextWriter(wsType)
if err != nil {
return nil, err
}
ret, err := newEncoder(w, packetType)
if err != nil {
return nil, err
}
return ret, nil
}
func (s *Server) Close() error {
return s.conn.Close()
}
func (s *Server) serveHTTP(w http.ResponseWriter, r *http.Request) {
defer s.callback.OnClose(s)
for {
t, r, err := s.conn.NextReader()
if err != nil {
s.conn.Close()
return
}
switch t {
case websocket.TextMessage:
fallthrough
case websocket.BinaryMessage:
decoder, err := parser.NewDecoder(r)
if err != nil {
return
}
s.callback.OnPacket(decoder)
decoder.Close()
}
}
}

View File

@ -0,0 +1,12 @@
package websocket
import (
"github.com/googollee/go-engine.io/transport"
)
var Creater = transport.Creater{
Name: "websocket",
Upgrading: true,
Server: NewServer,
Client: NewClient,
}

View File

@ -0,0 +1,458 @@
package websocket
import (
"encoding/hex"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"sync"
"testing"
"time"
"github.com/googollee/go-engine.io/transport"
"github.com/gorilla/websocket"
"github.com/googollee/go-engine.io/message"
"github.com/googollee/go-engine.io/parser"
. "github.com/smartystreets/goconvey/convey"
)
func TestWebsocket(t *testing.T) {
Convey("Creater", t, func() {
So(Creater.Name, ShouldEqual, "websocket")
So(Creater.Server, ShouldEqual, NewServer)
So(Creater.Client, ShouldEqual, NewClient)
})
Convey("Normal work, server part", t, func() {
sync := make(chan int)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
f := newFakeCallback()
s, err := NewServer(w, r, f)
if err != nil {
t.Fatal(err)
}
defer s.Close()
{
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
recoder := httptest.NewRecorder()
s.ServeHTTP(recoder, req)
if recoder.Code != http.StatusBadRequest {
t.Fatal(recoder.Code, "!=", http.StatusBadRequest)
}
}
{
w, err := s.NextWriter(message.MessageText, parser.OPEN)
if err != nil {
t.Fatal(err)
}
err = w.Close()
if err != nil {
t.Fatal(err)
}
}
{
<-f.onPacket
if f.messageType != message.MessageBinary {
t.Fatal(f.messageType, "!=", message.MessageBinary)
}
if f.packetType != parser.MESSAGE {
t.Fatal(f.packetType, "!=", parser.MESSAGE)
}
if f.err != nil {
t.Fatal(err)
}
if body := string(f.body); body != "测试" {
t.Fatal(body, "!=", "测试")
}
}
<-sync
sync <- 1
<-sync
sync <- 1
{
w, err := s.NextWriter(message.MessageBinary, parser.NOOP)
if err != nil {
t.Fatal(err)
}
err = w.Close()
if err != nil {
t.Fatal(err)
}
}
<-sync
sync <- 1
{
<-f.onPacket
if f.messageType != message.MessageText {
t.Fatal(f.messageType, "!=", message.MessageText)
}
if f.packetType != parser.MESSAGE {
t.Fatal(f.packetType, "!=", parser.MESSAGE)
}
if f.err != nil {
t.Fatal(err)
}
if body := hex.EncodeToString(f.body); body != "e697a5e69cace8aa9e" {
t.Fatal(body, "!=", "e697a5e69cace8aa9e")
}
}
<-sync
sync <- 1
}))
defer server.Close()
u, _ := url.Parse(server.URL)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String(), nil)
So(err, ShouldBeNil)
c, _ := NewClient(req)
defer c.Close()
{
w, _ := c.NextWriter(message.MessageBinary, parser.MESSAGE)
w.Write([]byte("测试"))
w.Close()
}
sync <- 1
<-sync
{
decoder, _ := c.NextReader()
defer decoder.Close()
ioutil.ReadAll(decoder)
}
sync <- 1
<-sync
{
decoder, _ := c.NextReader()
defer decoder.Close()
ioutil.ReadAll(decoder)
}
sync <- 1
<-sync
{
w, _ := c.NextWriter(message.MessageText, parser.MESSAGE)
w.Write([]byte("日本語"))
w.Close()
}
sync <- 1
<-sync
})
Convey("Normal work, client part", t, func() {
sync := make(chan int)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
f := newFakeCallback()
if v := r.URL.Query().Get("key"); v != "value" {
t.Fatal(v, "!=", "value")
}
s, _ := NewServer(w, r, f)
defer s.Close()
{
w, _ := s.NextWriter(message.MessageText, parser.OPEN)
w.Close()
}
{
<-f.onPacket
}
<-sync
sync <- 1
<-sync
sync <- 1
{
w, _ := s.NextWriter(message.MessageBinary, parser.NOOP)
w.Close()
}
<-sync
sync <- 1
{
<-f.onPacket
}
<-sync
sync <- 1
}))
defer server.Close()
u, err := url.Parse(server.URL)
So(err, ShouldBeNil)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String()+"/?key=value", nil)
So(err, ShouldBeNil)
c, err := NewClient(req)
So(err, ShouldBeNil)
defer c.Close()
So(c.Response(), ShouldNotBeNil)
So(c.Response().StatusCode, ShouldEqual, http.StatusSwitchingProtocols)
{
w, err := c.NextWriter(message.MessageBinary, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("测试"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
sync <- 1
<-sync
{
decoder, err := c.NextReader()
So(err, ShouldBeNil)
defer decoder.Close()
So(decoder.MessageType(), ShouldEqual, message.MessageText)
So(decoder.Type(), ShouldEqual, parser.OPEN)
b, err := ioutil.ReadAll(decoder)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "")
}
sync <- 1
<-sync
{
decoder, err := c.NextReader()
So(err, ShouldBeNil)
defer decoder.Close()
So(decoder.MessageType(), ShouldEqual, message.MessageBinary)
So(decoder.Type(), ShouldEqual, parser.NOOP)
b, err := ioutil.ReadAll(decoder)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "")
}
sync <- 1
<-sync
{
w, err := c.NextWriter(message.MessageText, parser.MESSAGE)
So(err, ShouldBeNil)
_, err = w.Write([]byte("日本語"))
So(err, ShouldBeNil)
err = w.Close()
So(err, ShouldBeNil)
}
sync <- 1
<-sync
})
Convey("Packet content", t, func() {
sync := make(chan int)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
f := newFakeCallback()
s, _ := NewServer(w, r, f)
defer s.Close()
{
w, _ := s.NextWriter(message.MessageText, parser.MESSAGE)
w.Write([]byte("日本語"))
w.Close()
}
sync <- 1
<-sync
}))
defer server.Close()
u, err := url.Parse(server.URL)
So(err, ShouldBeNil)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String(), nil)
So(err, ShouldBeNil)
c, err := NewClient(req)
So(err, ShouldBeNil)
defer c.Close()
{
client := c.(*client)
t, r, err := client.conn.NextReader()
So(err, ShouldBeNil)
So(t, ShouldEqual, websocket.TextMessage)
b, err := ioutil.ReadAll(r)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "4日本語")
So(hex.EncodeToString(b), ShouldEqual, "34e697a5e69cace8aa9e")
}
<-sync
sync <- 1
})
Convey("Close", t, func() {
f := newFakeCallback()
var s transport.Server
sync := make(chan int)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s, _ = NewServer(w, r, f)
s.Close()
s.Close()
s.Close()
sync <- 1
}))
defer server.Close()
u, err := url.Parse(server.URL)
So(err, ShouldBeNil)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String(), nil)
So(err, ShouldBeNil)
c, err := NewClient(req)
So(err, ShouldBeNil)
defer c.Close()
<-sync
waitForClose(f)
So(f.ClosedCount(), ShouldEqual, 1)
So(f.closeServer, ShouldEqual, s)
})
Convey("Closing by disconnected", t, func() {
f := newFakeCallback()
sync := make(chan int)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s, _ := NewServer(w, r, f)
server := s.(*Server)
server.conn.Close()
sync <- 1
}))
defer server.Close()
u, err := url.Parse(server.URL)
So(err, ShouldBeNil)
u.Scheme = "ws"
req, err := http.NewRequest("GET", u.String(), nil)
So(err, ShouldBeNil)
c, err := NewClient(req)
So(err, ShouldBeNil)
defer c.Close()
<-sync
waitForClose(f)
So(f.ClosedCount(), ShouldEqual, 1)
})
Convey("Closing writer after closed", t, func() {
f := newFakeCallback()
sync := make(chan int)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s, err := NewServer(w, r, f)
if err != nil {
t.Fatal(err)
}
writer, err := s.NextWriter(message.MessageText, parser.MESSAGE)
if err != nil {
t.Fatal(err)
}
err = s.Close()
if err != nil {
t.Fatal(err)
}
err = writer.Close()
if err == nil {
t.Fatal("err should not be nil")
}
sync <- 1
}))
defer server.Close()
u, _ := url.Parse(server.URL)
u.Scheme = "ws"
req, _ := http.NewRequest("GET", u.String(), nil)
c, _ := NewClient(req)
defer c.Close()
<-sync
})
}
func waitForClose(f *fakeCallback) {
timeout := time.After(5 * time.Second)
var closed bool
select {
case <-f.closedChan:
closed = true
case <-timeout:
}
So(closed, ShouldBeTrue)
}
type fakeCallback struct {
onPacket chan bool
messageType message.MessageType
packetType parser.PacketType
body []byte
err error
closedCount int
countLocker sync.Mutex
closeServer transport.Server
closedChan chan struct{}
}
func newFakeCallback() *fakeCallback {
return &fakeCallback{
onPacket: make(chan bool),
closedChan: make(chan struct{}),
}
}
func (f *fakeCallback) OnPacket(r *parser.PacketDecoder) {
f.packetType = r.Type()
f.messageType = r.MessageType()
f.body, f.err = ioutil.ReadAll(r)
f.onPacket <- true
}
func (f *fakeCallback) OnClose(s transport.Server) {
f.countLocker.Lock()
defer f.countLocker.Unlock()
f.closedCount++
f.closeServer = s
if f.closedCount == 1 {
close(f.closedChan)
}
}
func (f *fakeCallback) ClosedCount() int {
f.countLocker.Lock()
defer f.countLocker.Unlock()
return f.closedCount
}

5
vendor/github.com/googollee/go-socket.io/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,5 @@
language: go
go: 1.5
install:
- go get "github.com/smartystreets/goconvey/convey"
- go get -v .

23
vendor/github.com/googollee/go-socket.io/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2014-2014 Googol Lee <i@googol.im>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

127
vendor/github.com/googollee/go-socket.io/README.md generated vendored Normal file
View File

@ -0,0 +1,127 @@
# socket.io
[![GoDoc](http://godoc.org/github.com/googollee/go-socket.io?status.svg)](http://godoc.org/github.com/googollee/go-socket.io) [![Build Status](https://travis-ci.org/googollee/go-socket.io.svg)](https://travis-ci.org/googollee/go-socket.io)
**Please use v1.4 branch, or import "gopkg.in/googollee/go-socket.io.v1". I have no time to maintain master branch now**
go-socket.io is an implementation of [socket.io](http://socket.io) in golang, which is a realtime application framework.
It is compatible with latest implementation of socket.io in node.js, and supports room and namespace.
* for compatability with socket.io 0.9.x, please use branch 0.9.x *
## Install
Install the package with:
```bash
go get github.com/googollee/go-socket.io
```
Import it with:
```go
import "github.com/googollee/go-socket.io"
```
and use `socketio` as the package name inside the code.
## Example
Please check the example folder for details.
```go
package main
import (
"log"
"net/http"
"github.com/googollee/go-socket.io"
)
func main() {
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
log.Println("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
log.Println("emit:", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
so.On("disconnection", func() {
log.Println("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", server)
http.Handle("/", http.FileServer(http.Dir("./asset")))
log.Println("Serving at localhost:5000...")
log.Fatal(http.ListenAndServe(":5000", nil))
}
```
## Acknowledgements in go-socket.io 1.X.X
[See documentation about acknowledgements](http://socket.io/docs/#sending-and-getting-data-(acknowledgements))
##### Sending ACK with data from SERVER to CLIENT
* Client-side
```javascript
//using client-side socket.io-1.X.X.js
socket.emit('some:event', JSON.stringify(someData), function(data){
console.log('ACK from server wtih data: ', data));
});
```
* Server-side
```go
// The return type may vary depending on whether you will return
// In golang implementation of socket.io don't used callbacks for acknowledgement,
// but used return value, which wrapped into ack package and returned to the client's callback in JavaScript
so.On("some:event", func(msg string) string {
return msg //Sending ack with data in msg back to client, using "return statement"
})
```
##### Sending ACK with data from CLIENT to SERVER
* Client-side
```javascript
//using client-side socket.io-1.X.X.js
//last parameter of "on" handler is callback for sending ack to server with data or without data
socket.on('some:event', function (msg, sendAckCb) {
//Sending ACK with data to server after receiving some:event from server
sendAckCb(JSON.stringify(data)); // for example used serializing to JSON
}
```
* Server-side
```go
//You can use Emit or BroadcastTo with last parameter as callback for handling ack from client
//Sending packet to room "room_name" and event "some:event"
so.BroadcastTo("room_name", "some:event", dataForClient, func (so socketio.Socket, data string) {
log.Println("Client ACK with data: ", data)
})
// Or
so.Emit("some:event", dataForClient, func (so socketio.Socket, data string) {
log.Println("Client ACK with data: ", data)
})
```
## License
The 3-clause BSD License - see LICENSE for more details

70
vendor/github.com/googollee/go-socket.io/adapter.go generated vendored Normal file
View File

@ -0,0 +1,70 @@
package socketio
import "sync"
// BroadcastAdaptor is the adaptor to handle broadcasts.
type BroadcastAdaptor interface {
// Join causes the socket to join a room.
Join(room string, socket Socket) error
// Leave causes the socket to leave a room.
Leave(room string, socket Socket) error
// Send will send an event with args to the room. If "ignore" is not nil, the event will be excluded from being sent to "ignore".
Send(ignore Socket, room, event string, args ...interface{}) error
}
var newBroadcast = newBroadcastDefault
type broadcast struct {
m map[string]map[string]Socket
sync.RWMutex
}
func newBroadcastDefault() BroadcastAdaptor {
return &broadcast{
m: make(map[string]map[string]Socket),
}
}
func (b *broadcast) Join(room string, socket Socket) error {
b.Lock()
sockets, ok := b.m[room]
if !ok {
sockets = make(map[string]Socket)
}
sockets[socket.Id()] = socket
b.m[room] = sockets
b.Unlock()
return nil
}
func (b *broadcast) Leave(room string, socket Socket) error {
b.Lock()
defer b.Unlock()
sockets, ok := b.m[room]
if !ok {
return nil
}
delete(sockets, socket.Id())
if len(sockets) == 0 {
delete(b.m, room)
return nil
}
b.m[room] = sockets
return nil
}
func (b *broadcast) Send(ignore Socket, room, event string, args ...interface{}) error {
b.RLock()
sockets := b.m[room]
for id, s := range sockets {
if ignore != nil && ignore.Id() == id {
continue
}
s.Emit(event, args...)
}
b.RUnlock()
return nil
}

168
vendor/github.com/googollee/go-socket.io/attachment.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
package socketio
import (
"bytes"
"encoding/json"
"fmt"
"io"
"reflect"
)
// Attachment is an attachment handler used in emit args. All attachments will be sent as binary data in the transport layer. When using an attachment, make sure it is a pointer.
//
// For example:
//
// type Arg struct {
// Title string `json:"title"`
// File *Attachment `json:"file"`
// }
//
// f, _ := os.Open("./some_file")
// arg := Arg{
// Title: "some_file",
// File: &Attachment{
// Data: f,
// }
// }
//
// socket.Emit("send file", arg)
// socket.On("get file", func(so Socket, arg Arg) {
// b, _ := ioutil.ReadAll(arg.File.Data)
// })
type Attachment struct {
Data io.ReadWriter
num int
}
func encodeAttachments(v interface{}) []io.Reader {
index := 0
return encodeAttachmentValue(reflect.ValueOf(v), &index)
}
func encodeAttachmentValue(v reflect.Value, index *int) []io.Reader {
v = reflect.Indirect(v)
ret := []io.Reader{}
if !v.IsValid() {
return ret
}
switch v.Kind() {
case reflect.Struct:
if v.Type().Name() == "Attachment" {
a, ok := v.Addr().Interface().(*Attachment)
if !ok {
panic("can't convert")
}
a.num = *index
ret = append(ret, a.Data)
(*index)++
return ret
}
for i, n := 0, v.NumField(); i < n; i++ {
var r []io.Reader
r = encodeAttachmentValue(v.Field(i), index)
ret = append(ret, r...)
}
case reflect.Map:
if v.IsNil() {
return ret
}
for _, key := range v.MapKeys() {
var r []io.Reader
r = encodeAttachmentValue(v.MapIndex(key), index)
ret = append(ret, r...)
}
case reflect.Slice:
if v.IsNil() {
return ret
}
fallthrough
case reflect.Array:
for i, n := 0, v.Len(); i < n; i++ {
var r []io.Reader
r = encodeAttachmentValue(v.Index(i), index)
ret = append(ret, r...)
}
case reflect.Interface:
ret = encodeAttachmentValue(reflect.ValueOf(v.Interface()), index)
}
return ret
}
func decodeAttachments(v interface{}, binary [][]byte) error {
return decodeAttachmentValue(reflect.ValueOf(v), binary)
}
func decodeAttachmentValue(v reflect.Value, binary [][]byte) error {
v = reflect.Indirect(v)
if !v.IsValid() {
return fmt.Errorf("invalid value")
}
switch v.Kind() {
case reflect.Struct:
if v.Type().Name() == "Attachment" {
a, ok := v.Addr().Interface().(*Attachment)
if !ok {
panic("can't convert")
}
if a.num >= len(binary) || a.num < 0 {
return fmt.Errorf("out of range")
}
if a.Data == nil {
a.Data = bytes.NewBuffer(nil)
}
for b := binary[a.num]; len(b) > 0; {
n, err := a.Data.Write(b)
if err != nil {
return err
}
b = b[n:]
}
return nil
}
for i, n := 0, v.NumField(); i < n; i++ {
if err := decodeAttachmentValue(v.Field(i), binary); err != nil {
return err
}
}
case reflect.Map:
if v.IsNil() {
return nil
}
for _, key := range v.MapKeys() {
if err := decodeAttachmentValue(v.MapIndex(key), binary); err != nil {
return err
}
}
case reflect.Slice:
if v.IsNil() {
return nil
}
fallthrough
case reflect.Array:
for i, n := 0, v.Len(); i < n; i++ {
if err := decodeAttachmentValue(v.Index(i), binary); err != nil {
return err
}
}
case reflect.Interface:
if err := decodeAttachmentValue(reflect.ValueOf(v.Interface()), binary); err != nil {
return err
}
}
return nil
}
func (a Attachment) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("{\"_placeholder\":true,\"num\":%d}", a.num)), nil
}
func (a *Attachment) UnmarshalJSON(b []byte) error {
var v struct {
Num int `json:"num"`
}
if err := json.Unmarshal(b, &v); err != nil {
return err
}
a.num = v.Num
return nil
}

View File

@ -0,0 +1,184 @@
package socketio
import (
"bytes"
"encoding/json"
"io"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
type NoAttachment struct {
I int `json:"i"`
}
type HaveAttachment struct {
NoAttachment
A *Attachment `json:"a"`
}
func TestEncodeAttachments(t *testing.T) {
var input interface{}
var target []io.Reader
buf1 := bytes.NewBufferString("data1")
buf2 := bytes.NewBufferString("data2")
attachment1 := &Attachment{Data: buf1}
attachment2 := &Attachment{Data: buf2}
test := func() {
attachment1.num = -1
attachment2.num = -1
attachments := encodeAttachments(input)
if len(attachments)+len(target) > 0 {
So(attachments, ShouldResemble, target)
}
}
Convey("No attachment", t, func() {
input = &NoAttachment{}
target = nil
test()
})
Convey("Many attachment", t, func() {
input = &HaveAttachment{A: attachment1}
target = []io.Reader{buf1}
test()
So(attachment1.num, ShouldEqual, 0)
})
Convey("Array of attachments", t, func() {
input = [...]interface{}{HaveAttachment{A: attachment1}, &HaveAttachment{A: attachment2}}
target = []io.Reader{buf1, buf2}
test()
So(attachment1.num, ShouldEqual, 0)
So(attachment2.num, ShouldEqual, 1)
})
Convey("Slice of attachments", t, func() {
input = []interface{}{HaveAttachment{A: attachment1}, &HaveAttachment{A: attachment2}}
target = []io.Reader{buf1, buf2}
test()
So(attachment1.num, ShouldEqual, 0)
So(attachment2.num, ShouldEqual, 1)
})
Convey("Map of attachments", t, func() {
input = map[string]interface{}{"test": HaveAttachment{A: attachment1}, "testp": &HaveAttachment{A: attachment2}}
attachment1.num = -1
attachment2.num = -1
attachments := encodeAttachments(input)
So(attachment1.num, ShouldBeIn, []int{0, 1})
switch attachment1.num {
case 0:
So(attachment2.num, ShouldEqual, 1)
target = []io.Reader{buf1, buf2}
So(attachments, ShouldResemble, target)
case 1:
So(attachment2.num, ShouldEqual, 0)
target = []io.Reader{buf2, buf1}
So(attachments, ShouldResemble, target)
}
})
Convey("Encode attachment", t, func() {
input = map[string]interface{}{"test": HaveAttachment{A: attachment1}}
attachment1.num = -1
encodeAttachments(input)
b, err := json.Marshal(input)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, `{"test":{"i":0,"a":{"_placeholder":true,"num":0}}}`)
})
}
func TestDecodeAttachments(t *testing.T) {
var input [][]byte
var v interface{}
buf1 := bytes.NewBuffer(nil)
buf2 := bytes.NewBuffer(nil)
var attachment1 *Attachment
var attachment2 *Attachment
test := func() {
err := decodeAttachments(v, input)
So(err, ShouldBeNil)
if attachment1 != nil {
So(buf1.String(), ShouldEqual, "data1")
}
if attachment2 != nil {
So(buf2.String(), ShouldEqual, "data2")
}
buf1.Reset()
buf2.Reset()
}
Convey("No attachment", t, func() {
input = nil
v = NoAttachment{}
test()
})
Convey("Many attachment", t, func() {
input = [][]byte{[]byte("data1")}
attachment1 = &Attachment{Data: buf1}
attachment1.num = 0
v = HaveAttachment{A: attachment1}
test()
})
Convey("Array of attachments", t, func() {
input = [][]byte{[]byte("data1"), []byte("data2")}
attachment1 = &Attachment{Data: buf1}
attachment1.num = 0
attachment2 = &Attachment{Data: buf2}
attachment2.num = 1
v = [...]interface{}{HaveAttachment{A: attachment1}, &HaveAttachment{A: attachment2}}
test()
})
Convey("Slice of attachments", t, func() {
input = [][]byte{[]byte("data1"), []byte("data2")}
attachment1 = &Attachment{Data: buf1}
attachment1.num = 0
attachment2 = &Attachment{Data: buf2}
attachment2.num = 1
v = []interface{}{HaveAttachment{A: attachment1}, &HaveAttachment{A: attachment2}}
test()
})
Convey("Map of attachments", t, func() {
input = [][]byte{[]byte("data1"), []byte("data2")}
attachment1 = &Attachment{Data: buf1}
attachment1.num = 0
attachment2 = &Attachment{Data: buf2}
attachment2.num = 1
v = map[string]interface{}{"test": HaveAttachment{A: attachment1}, "testp": &HaveAttachment{A: attachment2}}
test()
})
Convey("Deocde json", t, func() {
b := []byte(`{"i":0,"a":{"_placeholder":true,"num":2}}`)
v := &HaveAttachment{}
err := json.Unmarshal(b, &v)
So(err, ShouldBeNil)
So(v.A.num, ShouldEqual, 2)
})
}

82
vendor/github.com/googollee/go-socket.io/caller.go generated vendored Normal file
View File

@ -0,0 +1,82 @@
package socketio
import (
"errors"
"fmt"
"reflect"
)
type caller struct {
Func reflect.Value
Args []reflect.Type
NeedSocket bool
}
func newCaller(f interface{}) (*caller, error) {
fv := reflect.ValueOf(f)
if fv.Kind() != reflect.Func {
return nil, fmt.Errorf("f is not func")
}
ft := fv.Type()
if ft.NumIn() == 0 {
return &caller{
Func: fv,
}, nil
}
args := make([]reflect.Type, ft.NumIn())
for i, n := 0, ft.NumIn(); i < n; i++ {
args[i] = ft.In(i)
}
needSocket := false
if args[0].Name() == "Socket" {
args = args[1:]
needSocket = true
}
return &caller{
Func: fv,
Args: args,
NeedSocket: needSocket,
}, nil
}
func (c *caller) GetArgs() []interface{} {
ret := make([]interface{}, len(c.Args))
for i, argT := range c.Args {
if argT.Kind() == reflect.Ptr {
argT = argT.Elem()
}
v := reflect.New(argT)
ret[i] = v.Interface()
}
return ret
}
func (c *caller) Call(so Socket, args []interface{}) []reflect.Value {
var a []reflect.Value
diff := 0
if c.NeedSocket {
diff = 1
a = make([]reflect.Value, len(args)+1)
a[0] = reflect.ValueOf(so)
} else {
a = make([]reflect.Value, len(args))
}
if len(args) != len(c.Args) {
return []reflect.Value{reflect.ValueOf([]interface{}{}), reflect.ValueOf(errors.New("Arguments do not match"))}
}
for i, arg := range args {
v := reflect.ValueOf(arg)
if c.Args[i].Kind() != reflect.Ptr {
if v.IsValid() {
v = v.Elem()
} else {
v = reflect.Zero(c.Args[i])
}
}
a[i+diff] = v
}
return c.Func.Call(a)
}

View File

@ -0,0 +1,38 @@
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
<script src="/socket.io-1.3.7.js"></script>
<script src="/jquery-1.11.1.js"></script>
<script>
var socket = io();
$('form').submit(function(){
socket.emit('chat message with ack', $('#m').val(), function(data){
$('#messages').append($('<li>').text('ACK CALLBACK: ' + data));
});
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,53 @@
package main
import (
"fmt"
"log"
"net/http"
"github.com/googollee/go-socket.io"
)
func main() {
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
log.Println("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
m := make(map[string]interface{})
m["a"] = "你好"
e := so.Emit("cn1111", m)
//这个没有问题
fmt.Println("\n\n")
b := make(map[string]string)
b["u-a"] = "中文内容" //这个不能是中文
m["b-c"] = b
e = so.Emit("cn2222", m)
log.Println(e)
log.Println("emit:", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
// Socket.io acknowledgement example
// The return type may vary depending on whether you will return
// For this example it is "string" type
so.On("chat message with ack", func(msg string) string {
return msg
})
so.On("disconnection", func() {
log.Println("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", server)
http.Handle("/", http.FileServer(http.Dir("./asset")))
log.Println("Serving at localhost:5000...")
log.Fatal(http.ListenAndServe(":5000", nil))
}

213
vendor/github.com/googollee/go-socket.io/handler.go generated vendored Normal file
View File

@ -0,0 +1,213 @@
package socketio
import (
"fmt"
"reflect"
"sync"
)
type baseHandler struct {
events map[string]*caller
name string
broadcast BroadcastAdaptor
evMu sync.Mutex
}
func newBaseHandler(name string, broadcast BroadcastAdaptor) *baseHandler {
return &baseHandler{
events: make(map[string]*caller),
name: name,
broadcast: broadcast,
evMu: sync.Mutex{},
}
}
// On registers the function f to handle an event.
func (h *baseHandler) On(event string, f interface{}) error {
c, err := newCaller(f)
if err != nil {
return err
}
h.evMu.Lock()
h.events[event] = c
h.evMu.Unlock()
return nil
}
type socketHandler struct {
*baseHandler
acksmu sync.Mutex
acks map[int]*caller
socket *socket
rooms map[string]struct{}
}
func newSocketHandler(s *socket, base *baseHandler) *socketHandler {
events := make(map[string]*caller)
base.evMu.Lock()
for k, v := range base.events {
events[k] = v
}
base.evMu.Unlock()
return &socketHandler{
baseHandler: &baseHandler{
events: events,
broadcast: base.broadcast,
evMu: base.evMu,
},
acks: make(map[int]*caller),
socket: s,
rooms: make(map[string]struct{}),
}
}
func (h *socketHandler) Emit(event string, args ...interface{}) error {
var c *caller
if l := len(args); l > 0 {
fv := reflect.ValueOf(args[l-1])
if fv.Kind() == reflect.Func {
var err error
c, err = newCaller(args[l-1])
if err != nil {
return err
}
args = args[:l-1]
}
}
args = append([]interface{}{event}, args...)
if c != nil {
id, err := h.socket.sendId(args)
if err != nil {
return err
}
h.acksmu.Lock()
h.acks[id] = c
h.acksmu.Unlock()
return nil
}
return h.socket.send(args)
}
func (h *socketHandler) Rooms() []string {
ret := make([]string, len(h.rooms))
i := 0
for room := range h.rooms {
ret[i] = room
i++
}
return ret
}
func (h *socketHandler) Join(room string) error {
if err := h.baseHandler.broadcast.Join(h.broadcastName(room), h.socket); err != nil {
return err
}
h.rooms[room] = struct{}{}
return nil
}
func (h *socketHandler) Leave(room string) error {
if err := h.baseHandler.broadcast.Leave(h.broadcastName(room), h.socket); err != nil {
return err
}
delete(h.rooms, room)
return nil
}
func (h *socketHandler) LeaveAll() error {
for room := range h.rooms {
if err := h.baseHandler.broadcast.Leave(h.broadcastName(room), h.socket); err != nil {
return err
}
}
return nil
}
func (h *baseHandler) BroadcastTo(room, event string, args ...interface{}) error {
return h.broadcast.Send(nil, h.broadcastName(room), event, args...)
}
func (h *socketHandler) BroadcastTo(room, event string, args ...interface{}) error {
return h.baseHandler.broadcast.Send(h.socket, h.broadcastName(room), event, args...)
}
func (h *baseHandler) broadcastName(room string) string {
return fmt.Sprintf("%s:%s", h.name, room)
}
func (h *socketHandler) onPacket(decoder *decoder, packet *packet) ([]interface{}, error) {
var message string
switch packet.Type {
case _CONNECT:
message = "connection"
case _DISCONNECT:
message = "disconnection"
case _ERROR:
message = "error"
case _ACK:
fallthrough
case _BINARY_ACK:
return nil, h.onAck(packet.Id, decoder, packet)
default:
if decoder != nil {
message = decoder.Message()
}
}
h.evMu.Lock()
c, ok := h.events[message]
h.evMu.Unlock()
if !ok {
// If the message is not recognized by the server, the decoder.currentCloser
// needs to be closed otherwise the server will be stuck until the e
if decoder != nil {
decoder.Close()
}
return nil, nil
}
args := c.GetArgs()
olen := len(args)
if olen > 0 && decoder != nil {
packet.Data = &args
if err := decoder.DecodeData(packet); err != nil {
return nil, err
}
}
for i := len(args); i < olen; i++ {
args = append(args, nil)
}
retV := c.Call(h.socket, args)
if len(retV) == 0 {
return nil, nil
}
var err error
if last, ok := retV[len(retV)-1].Interface().(error); ok {
err = last
retV = retV[0 : len(retV)-1]
}
ret := make([]interface{}, len(retV))
for i, v := range retV {
ret[i] = v.Interface()
}
return ret, err
}
func (h *socketHandler) onAck(id int, decoder *decoder, packet *packet) error {
h.acksmu.Lock()
c, ok := h.acks[id]
if !ok {
h.acksmu.Unlock()
return nil
}
delete(h.acks, id)
h.acksmu.Unlock()
args := c.GetArgs()
packet.Data = &args
if err := decoder.DecodeData(packet); err != nil {
return err
}
c.Call(h.socket, args)
return nil
}

View File

@ -0,0 +1,105 @@
package socketio
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
"net/http"
"io"
"github.com/googollee/go-engine.io"
)
type FakeBroadcastAdaptor struct {}
func (f *FakeBroadcastAdaptor) Join(room string, socket Socket) error {
return nil
}
func (f *FakeBroadcastAdaptor) Leave(room string, socket Socket) error {
return nil
}
func (f *FakeBroadcastAdaptor) Send(ignore Socket, room, event string, args ...interface{}) error {
return nil
}
type FakeReadCloser struct {}
func (fr *FakeReadCloser) Read(p []byte) (n int, err error) {
p = append(p, byte(128))
return 1, nil
}
func (fr *FakeReadCloser) Close() error {
return nil
}
type FakeWriteCloser struct {}
func (fr *FakeWriteCloser) Write(p []byte) (n int, err error) {
return len(p), nil
}
func (fr *FakeWriteCloser) Close() error {
return nil
}
type FakeSockConnection struct {}
func (f *FakeSockConnection) Id() string {
return "test1"
}
func (f *FakeSockConnection) Request() *http.Request {
return &http.Request{}
}
func (f *FakeSockConnection) Close() error {
return nil
}
func (f *FakeSockConnection) NextReader() (engineio.MessageType, io.ReadCloser, error) {
return engineio.MessageText, &FakeReadCloser{}, nil
}
func (f *FakeSockConnection) NextWriter(messageType engineio.MessageType) (io.WriteCloser, error) {
return &FakeWriteCloser{}, nil
}
func TestHandler(t *testing.T) {
//BugFix missed
//Method: handler.onPacket
//Reason: missed fallthrough after case _ACK:
//
// case _ACK:
// fallthrough <---- fixed problem
//
Convey("Call ACK handler by ACK id received from client", t, func() {
saver := &FrameSaver{}
var handlerCalled bool
baseHandlerInstance := newBaseHandler("some:event", &FakeBroadcastAdaptor{})
socketInstance := newSocket(&FakeSockConnection{}, baseHandlerInstance)
c, _ := newCaller(func () {handlerCalled = true})
socketInstance.acks[0] = c
socketInstance.onPacket(newDecoder(saver), &packet{Type:_ACK, Id:0, Data:"[]", NSP:"/"})
So(len(socketInstance.acks), ShouldEqual, 0)
So(handlerCalled, ShouldBeTrue)
})
Convey("Call BINARY ACK handler by BINARY ACK id received from client", t, func() {
saver := &FrameSaver{}
var handlerCalled bool
baseHandlerInstance := newBaseHandler("some:event", &FakeBroadcastAdaptor{})
socketInstance := newSocket(&FakeSockConnection{}, baseHandlerInstance)
c, _ := newCaller(func () {handlerCalled = true})
socketInstance.acks[0] = c
socketInstance.onPacket(newDecoder(saver), &packet{Type:_BINARY_ACK, Id:0, Data:"[]", NSP:"/"})
So(len(socketInstance.acks), ShouldEqual, 0)
So(handlerCalled, ShouldBeTrue)
})
}

View File

@ -0,0 +1,50 @@
package socketio
import (
"bytes"
"io"
"io/ioutil"
"github.com/googollee/go-engine.io"
)
type WriterNopCloser struct {
io.Writer
}
func NewWriterNopCloser(w io.Writer) io.WriteCloser {
return WriterNopCloser{
Writer: w,
}
}
func (w WriterNopCloser) Close() error {
return nil
}
type FrameData struct {
Buffer *bytes.Buffer
Type engineio.MessageType
}
type FrameSaver struct {
data []FrameData
}
func (f *FrameSaver) NextWriter(t engineio.MessageType) (io.WriteCloser, error) {
data := FrameData{
Buffer: bytes.NewBuffer(nil),
Type: t,
}
f.data = append(f.data, data)
return NewWriterNopCloser(data.Buffer), nil
}
func (f *FrameSaver) NextReader() (engineio.MessageType, io.ReadCloser, error) {
if len(f.data) == 0 {
return engineio.MessageText, nil, io.EOF
}
ret := f.data[0]
f.data = f.data[1:]
return ret.Type, ioutil.NopCloser(ret.Buffer), nil
}

34
vendor/github.com/googollee/go-socket.io/ioutil.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package socketio
import (
"io"
)
type writerHelper struct {
writer io.Writer
err error
}
func newWriterHelper(w io.Writer) *writerHelper {
return &writerHelper{
writer: w,
}
}
func (h *writerHelper) Write(p []byte) {
if h.err != nil {
return
}
for len(p) > 0 {
n, err := h.writer.Write(p)
if err != nil {
h.err = err
return
}
p = p[n:]
}
}
func (h *writerHelper) Error() error {
return h.err
}

6
vendor/github.com/googollee/go-socket.io/main.go generated vendored Normal file
View File

@ -0,0 +1,6 @@
/*
go-socket.io is a server implementation of socket.io in golang.
It is compatible with the official Node.js implementation.
*/
package socketio

View File

@ -0,0 +1,60 @@
package socketio
import (
"bufio"
)
type messageReader struct {
reader *bufio.Reader
message string
firstRead bool
}
func newMessageReader(bufr *bufio.Reader) (*messageReader, error) {
if _, err := bufr.ReadBytes('"'); err != nil {
return nil, err
}
msg, err := bufr.ReadBytes('"')
if err != nil {
return nil, err
}
for {
b, err := bufr.Peek(1)
if err != nil {
return nil, err
}
if b[0] == ',' {
bufr.ReadByte()
break
}
if b[0] != ' ' {
break
}
bufr.ReadByte()
}
return &messageReader{
reader: bufr,
message: string(msg[:len(msg)-1]),
firstRead: true,
}, nil
}
func (r *messageReader) Message() string {
return r.message
}
func (r *messageReader) Read(b []byte) (int, error) {
if len(b) == 0 {
return 0, nil
}
if r.firstRead {
r.firstRead = false
b[0] = '['
n, err := r.reader.Read(b[1:])
if err != nil {
return -1, err
}
return n + 1, err
}
return r.reader.Read(b)
}

View File

@ -0,0 +1,54 @@
package socketio
import (
"bufio"
"bytes"
"io/ioutil"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestMessageReader(t *testing.T) {
Convey("Read with args", t, func() {
buf := bufio.NewReader(bytes.NewBufferString(`["message",1]`))
reader, err := newMessageReader(buf)
So(err, ShouldBeNil)
So(reader.Message(), ShouldEqual, "message")
b, err := ioutil.ReadAll(reader)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "[1]")
})
Convey("Read with args, space", t, func() {
buf := bufio.NewReader(bytes.NewBufferString(`["message" , 1]`))
reader, err := newMessageReader(buf)
So(err, ShouldBeNil)
So(reader.Message(), ShouldEqual, "message")
b, err := ioutil.ReadAll(reader)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "[ 1]")
})
Convey("Read only message", t, func() {
buf := bufio.NewReader(bytes.NewBufferString(`["message"]`))
reader, err := newMessageReader(buf)
So(err, ShouldBeNil)
So(reader.Message(), ShouldEqual, "message")
b, err := ioutil.ReadAll(reader)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "[]")
})
Convey("Read only message", t, func() {
buf := bufio.NewReader(bytes.NewBufferString(`["message" ]`))
reader, err := newMessageReader(buf)
So(err, ShouldBeNil)
So(reader.Message(), ShouldEqual, "message")
b, err := ioutil.ReadAll(reader)
So(err, ShouldBeNil)
So(string(b), ShouldEqual, "[]")
})
}

47
vendor/github.com/googollee/go-socket.io/namespace.go generated vendored Normal file
View File

@ -0,0 +1,47 @@
package socketio
// Namespace is the name space of a socket.io handler.
type Namespace interface {
// Name returns the name of the namespace.
Name() string
// Of returns the namespace with given name.
Of(name string) Namespace
// On registers the function f to handle an event.
On(event string, f interface{}) error
}
type namespace struct {
*baseHandler
root map[string]Namespace
}
func newNamespace(broadcast BroadcastAdaptor) *namespace {
ret := &namespace{
baseHandler: newBaseHandler("", broadcast),
root: make(map[string]Namespace),
}
ret.root[ret.Name()] = ret
return ret
}
func (n *namespace) Name() string {
return n.baseHandler.name
}
func (n *namespace) Of(name string) Namespace {
if name == "/" {
name = ""
}
if ret, ok := n.root[name]; ok {
return ret
}
ret := &namespace{
baseHandler: newBaseHandler(name, n.baseHandler.broadcast),
root: n.root,
}
n.root[name] = ret
return ret
}

338
vendor/github.com/googollee/go-socket.io/parser.go generated vendored Normal file
View File

@ -0,0 +1,338 @@
package socketio
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strconv"
"github.com/googollee/go-engine.io"
)
const Protocol = 4
type packetType int
const (
_CONNECT packetType = iota
_DISCONNECT
_EVENT
_ACK
_ERROR
_BINARY_EVENT
_BINARY_ACK
)
func (t packetType) String() string {
switch t {
case _CONNECT:
return "connect"
case _DISCONNECT:
return "disconnect"
case _EVENT:
return "event"
case _ACK:
return "ack"
case _ERROR:
return "error"
case _BINARY_EVENT:
return "binary_event"
case _BINARY_ACK:
return "binary_ack"
}
return fmt.Sprintf("unknown(%d)", t)
}
type frameReader interface {
NextReader() (engineio.MessageType, io.ReadCloser, error)
}
type frameWriter interface {
NextWriter(engineio.MessageType) (io.WriteCloser, error)
}
type packet struct {
Type packetType
NSP string
Id int
Data interface{}
attachNumber int
}
type encoder struct {
w frameWriter
err error
}
func newEncoder(w frameWriter) *encoder {
return &encoder{
w: w,
}
}
func (e *encoder) Encode(v packet) error {
attachments := encodeAttachments(v.Data)
v.attachNumber = len(attachments)
if v.attachNumber > 0 {
v.Type += _BINARY_EVENT - _EVENT
}
if err := e.encodePacket(v); err != nil {
return err
}
for _, a := range attachments {
if err := e.writeBinary(a); err != nil {
return err
}
}
return nil
}
func (e *encoder) encodePacket(v packet) error {
writer, err := e.w.NextWriter(engineio.MessageText)
if err != nil {
return err
}
defer writer.Close()
w := newTrimWriter(writer, "\n")
wh := newWriterHelper(w)
wh.Write([]byte{byte(v.Type) + '0'})
if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
wh.Write([]byte(fmt.Sprintf("%d-", v.attachNumber)))
}
needEnd := false
if v.NSP != "" {
wh.Write([]byte(v.NSP))
needEnd = true
}
if v.Id >= 0 {
f := "%d"
if needEnd {
f = ",%d"
needEnd = false
}
wh.Write([]byte(fmt.Sprintf(f, v.Id)))
}
if v.Data != nil {
if needEnd {
wh.Write([]byte{','})
needEnd = false
}
if wh.Error() != nil {
return wh.Error()
}
encoder := json.NewEncoder(w)
return encoder.Encode(v.Data)
}
return wh.Error()
}
func (e *encoder) writeBinary(r io.Reader) error {
writer, err := e.w.NextWriter(engineio.MessageBinary)
if err != nil {
return err
}
defer writer.Close()
if _, err := io.Copy(writer, r); err != nil {
return err
}
return nil
}
type decoder struct {
reader frameReader
message string
current io.Reader
currentCloser io.Closer
}
func newDecoder(r frameReader) *decoder {
return &decoder{
reader: r,
}
}
func (d *decoder) Close() {
if d != nil && d.currentCloser != nil {
d.currentCloser.Close()
d.current = nil
d.currentCloser = nil
}
}
func (d *decoder) Decode(v *packet) error {
ty, r, err := d.reader.NextReader()
if err != nil {
return err
}
if d.current != nil {
d.Close()
}
defer func() {
if d.current == nil {
r.Close()
}
}()
if ty != engineio.MessageText {
return fmt.Errorf("need text package")
}
reader := bufio.NewReader(r)
v.Id = -1
t, err := reader.ReadByte()
if err != nil {
return err
}
v.Type = packetType(t - '0')
if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
num, err := reader.ReadBytes('-')
if err != nil {
return err
}
numLen := len(num)
if numLen == 0 {
return fmt.Errorf("invalid packet")
}
n, err := strconv.ParseInt(string(num[:numLen-1]), 10, 64)
if err != nil {
return fmt.Errorf("invalid packet")
}
v.attachNumber = int(n)
}
next, err := reader.Peek(1)
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if len(next) == 0 {
return fmt.Errorf("invalid packet")
}
if next[0] == '/' {
path, err := reader.ReadBytes(',')
if err != nil && err != io.EOF {
return err
}
pathLen := len(path)
if pathLen == 0 {
return fmt.Errorf("invalid packet")
}
if err == nil {
path = path[:pathLen-1]
}
v.NSP = string(path)
if err == io.EOF {
return nil
}
}
id := bytes.NewBuffer(nil)
finish := false
for {
next, err := reader.Peek(1)
if err == io.EOF {
finish = true
break
}
if err != nil {
return err
}
if '0' <= next[0] && next[0] <= '9' {
if err := id.WriteByte(next[0]); err != nil {
return err
}
} else {
break
}
reader.ReadByte()
}
if id.Len() > 0 {
id, err := strconv.ParseInt(id.String(), 10, 64)
if err != nil {
return err
}
v.Id = int(id)
}
if finish {
return nil
}
switch v.Type {
case _EVENT:
fallthrough
case _BINARY_EVENT:
msgReader, err := newMessageReader(reader)
if err != nil {
return err
}
d.message = msgReader.Message()
d.current = msgReader
d.currentCloser = r
case _ACK:
fallthrough
case _BINARY_ACK:
d.current = reader
d.currentCloser = r
}
return nil
}
func (d *decoder) Message() string {
return d.message
}
func (d *decoder) DecodeData(v *packet) error {
if d.current == nil {
return nil
}
defer func() {
d.Close()
}()
decoder := json.NewDecoder(d.current)
if err := decoder.Decode(v.Data); err != nil {
return err
}
if v.Type == _BINARY_EVENT || v.Type == _BINARY_ACK {
binary, err := d.decodeBinary(v.attachNumber)
if err != nil {
return err
}
if err := decodeAttachments(v.Data, binary); err != nil {
return err
}
v.Type -= _BINARY_EVENT - _EVENT
}
return nil
}
func (d *decoder) decodeBinary(num int) ([][]byte, error) {
ret := make([][]byte, num)
for i := 0; i < num; i++ {
d.currentCloser.Close()
t, r, err := d.reader.NextReader()
if err != nil {
return nil, err
}
d.currentCloser = r
if t == engineio.MessageText {
return nil, fmt.Errorf("need binary")
}
b, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}
ret[i] = b
}
return ret, nil
}

191
vendor/github.com/googollee/go-socket.io/parser_test.go generated vendored Normal file
View File

@ -0,0 +1,191 @@
package socketio
import (
"bytes"
"github.com/googollee/go-engine.io"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestPacketType(t *testing.T) {
Convey("Type string", t, func() {
So(_CONNECT, ShouldEqual, 0)
So(_CONNECT.String(), ShouldEqual, "connect")
So(_DISCONNECT, ShouldEqual, 1)
So(_DISCONNECT.String(), ShouldEqual, "disconnect")
So(_EVENT, ShouldEqual, 2)
So(_EVENT.String(), ShouldEqual, "event")
So(_ACK, ShouldEqual, 3)
So(_ACK.String(), ShouldEqual, "ack")
So(_ERROR, ShouldEqual, 4)
So(_ERROR.String(), ShouldEqual, "error")
So(_BINARY_EVENT, ShouldEqual, 5)
So(_BINARY_EVENT.String(), ShouldEqual, "binary_event")
So(_BINARY_ACK, ShouldEqual, 6)
So(_BINARY_ACK.String(), ShouldEqual, "binary_ack")
})
}
func TestParser(t *testing.T) {
p := packet{}
var decodeData interface{}
output := ""
message := ""
test := func() {
saver := &FrameSaver{}
encoder := newEncoder(saver)
err := encoder.Encode(p)
So(err, ShouldBeNil)
So(len(saver.data), ShouldBeGreaterThan, 0)
So(saver.data[0].Buffer.String(), ShouldEqual, output)
So(saver.data[0].Type, ShouldEqual, engineio.MessageText)
if len(saver.data) > 1 {
So(saver.data[1].Buffer.String(), ShouldEqual, "data")
So(saver.data[1].Type, ShouldEqual, engineio.MessageBinary)
}
d := packet{Data: decodeData}
decoder := newDecoder(saver)
err = decoder.Decode(&d)
So(err, ShouldBeNil)
So(d.Id, ShouldEqual, p.Id)
So(d.NSP, ShouldEqual, p.NSP)
if decodeData == nil {
So(d.Data, ShouldBeNil)
}
So(decoder.Message(), ShouldEqual, message)
err = decoder.DecodeData(&d)
So(err, ShouldBeNil)
So(d.Type, ShouldEqual, p.Type)
So(decoder.current, ShouldBeNil)
}
Convey("Only type", t, func() {
p = packet{
Type: _CONNECT,
Id: -1,
}
decodeData = nil
output = "0"
message = ""
test()
})
Convey("Type and id", t, func() {
p = packet{
Type: _EVENT,
Id: 1,
}
decodeData = nil
output = "21"
message = ""
test()
})
Convey("Type and namespace", t, func() {
p = packet{
Type: _EVENT,
Id: -1,
NSP: "/abc",
}
decodeData = nil
output = "2/abc"
message = ""
test()
})
Convey("Type, id and namespace", t, func() {
p = packet{
Type: _EVENT,
Id: 1,
NSP: "/abc",
}
decodeData = nil
output = "2/abc,1"
message = ""
test()
})
Convey("Type, namespace and data", t, func() {
p = packet{
Type: _EVENT,
Id: -1,
NSP: "/abc",
Data: []interface{}{"numbers", 1, 2, 3},
}
var i1, i2, i3 int
decodeData = &[]interface{}{&i1, &i2, &i3}
output = "2/abc,[\"numbers\",1,2,3]"
message = "numbers"
test()
So(i1, ShouldEqual, 1)
So(i2, ShouldEqual, 2)
So(i3, ShouldEqual, 3)
})
Convey("Type, namespace, id and data", t, func() {
p = packet{
Type: _EVENT,
Id: 1,
NSP: "/abc",
Data: []interface{}{"numbers", 1, 2, 3},
}
var i1, i2, i3 int
decodeData = &[]interface{}{&i1, &i2, &i3}
output = "2/abc,1[\"numbers\",1,2,3]"
message = "numbers"
test()
So(i1, ShouldEqual, 1)
So(i2, ShouldEqual, 2)
So(i3, ShouldEqual, 3)
})
Convey("Type, namespace, id and data(ack)", t, func() {
p = packet{
Type: _ACK,
Id: 1,
NSP: "/abc",
Data: []interface{}{1, 2, 3},
}
var i1, i2, i3 int
decodeData = &[]interface{}{&i1, &i2, &i3}
output = "3/abc,1[1,2,3]"
message = ""
test()
So(i1, ShouldEqual, 1)
So(i2, ShouldEqual, 2)
So(i3, ShouldEqual, 3)
})
Convey("Binary type with attachment", t, func() {
p = packet{
Type: _EVENT,
Id: 1,
NSP: "/abc",
Data: []interface{}{"binary", &Attachment{Data: bytes.NewBufferString("data")}},
}
buf := bytes.NewBuffer(nil)
decodeData = &[]interface{}{&Attachment{Data: buf}}
output = `51-/abc,1["binary",{"_placeholder":true,"num":0}]`
message = "binary"
test()
So(buf.String(), ShouldEqual, "data")
})
}

106
vendor/github.com/googollee/go-socket.io/server.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
package socketio
import (
"github.com/googollee/go-engine.io"
"net/http"
"time"
)
// Server is the server of socket.io.
type Server struct {
*namespace
broadcast BroadcastAdaptor
eio *engineio.Server
}
// NewServer returns the server supported given transports. If transports is nil, the server will use ["polling", "websocket"] as default.
func NewServer(transportNames []string) (*Server, error) {
eio, err := engineio.NewServer(transportNames)
if err != nil {
return nil, err
}
ret := &Server{
namespace: newNamespace(newBroadcastDefault()),
eio: eio,
}
go ret.loop()
return ret, nil
}
// SetPingTimeout sets the timeout of a connection ping. When it times out, the server will close the connection with the client. Default is 60s.
func (s *Server) SetPingTimeout(t time.Duration) {
s.eio.SetPingTimeout(t)
}
// SetPingInterval sets the interval of pings. Default is 25s.
func (s *Server) SetPingInterval(t time.Duration) {
s.eio.SetPingInterval(t)
}
// SetMaxConnection sets the maximum number of connections with clients. Default is 1000.
func (s *Server) SetMaxConnection(n int) {
s.eio.SetMaxConnection(n)
}
// GetMaxConnection returns the current max connection
func (s *Server) GetMaxConnection() int {
return s.eio.GetMaxConnection()
}
// Count returns the current number of connected clients in session
func (s *Server) Count() int {
return s.eio.Count()
}
// SetAllowRequest sets the middleware function when a connection is established. If a non-nil value is returned, the connection won't be established. Default will allow all connections.
func (s *Server) SetAllowRequest(f func(*http.Request) error) {
s.eio.SetAllowRequest(f)
}
// SetAllowUpgrades sets whether server allows transport upgrades. Default is true.
func (s *Server) SetAllowUpgrades(allow bool) {
s.eio.SetAllowUpgrades(allow)
}
// SetCookie sets the name of the cookie used by engine.io. Default is "io".
func (s *Server) SetCookie(prefix string) {
s.eio.SetCookie(prefix)
}
// SetNewId sets the callback func to generate new connection id. By default, id is generated from remote address + current time stamp
func (s *Server) SetNewId(f func(*http.Request) string) {
s.eio.SetNewId(f)
}
// SetSessionsManager sets the sessions as server's session manager. Default sessions is a single process manager. You can customize it as a load balancer.
func (s *Server) SetSessionManager(sessions engineio.Sessions) {
s.eio.SetSessionManager(sessions)
}
// SetAdaptor sets the adaptor of broadcast. Default is an in-process broadcast implementation.
func (s *Server) SetAdaptor(adaptor BroadcastAdaptor) {
s.namespace = newNamespace(adaptor)
}
// ServeHTTP handles http requests.
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.eio.ServeHTTP(w, r)
}
// BroadcastTo is a server level broadcast function.
func (s *Server) BroadcastTo(room, message string, args ...interface{}) {
s.namespace.BroadcastTo(room, message, args...)
}
func (s *Server) loop() {
for {
conn, err := s.eio.Accept()
if err != nil {
return
}
s := newSocket(conn, s.baseHandler)
go func(s *socket) {
s.loop()
}(s)
}
}

174
vendor/github.com/googollee/go-socket.io/socket.go generated vendored Normal file
View File

@ -0,0 +1,174 @@
package socketio
import (
"net/http"
"sync"
"github.com/googollee/go-engine.io"
)
// Socket is the socket object of socket.io.
type Socket interface {
// Id returns the session id of socket.
Id() string
// Rooms returns the rooms name joined now.
Rooms() []string
// Request returns the first http request when established connection.
Request() *http.Request
// On registers the function f to handle an event.
On(event string, f interface{}) error
// Emit emits an event with given args.
Emit(event string, args ...interface{}) error
// Join joins the room.
Join(room string) error
// Leave leaves the room.
Leave(room string) error
// Disconnect disconnect the socket.
Disconnect()
// BroadcastTo broadcasts an event to the room with given args.
BroadcastTo(room, event string, args ...interface{}) error
}
type socket struct {
*socketHandler
conn engineio.Conn
namespace string
id int
mu sync.Mutex
}
func newSocket(conn engineio.Conn, base *baseHandler) *socket {
ret := &socket{
conn: conn,
}
ret.socketHandler = newSocketHandler(ret, base)
return ret
}
func (s *socket) Id() string {
return s.conn.Id()
}
func (s *socket) Request() *http.Request {
return s.conn.Request()
}
func (s *socket) Emit(event string, args ...interface{}) error {
if err := s.socketHandler.Emit(event, args...); err != nil {
return err
}
if event == "disconnect" {
s.conn.Close()
}
return nil
}
func (s *socket) Disconnect() {
s.conn.Close()
}
func (s *socket) send(args []interface{}) error {
packet := packet{
Type: _EVENT,
Id: -1,
NSP: s.namespace,
Data: args,
}
encoder := newEncoder(s.conn)
return encoder.Encode(packet)
}
func (s *socket) sendConnect() error {
packet := packet{
Type: _CONNECT,
Id: -1,
NSP: s.namespace,
}
encoder := newEncoder(s.conn)
return encoder.Encode(packet)
}
func (s *socket) sendId(args []interface{}) (int, error) {
s.mu.Lock()
packet := packet{
Type: _EVENT,
Id: s.id,
NSP: s.namespace,
Data: args,
}
s.id++
if s.id < 0 {
s.id = 0
}
s.mu.Unlock()
encoder := newEncoder(s.conn)
err := encoder.Encode(packet)
if err != nil {
return -1, nil
}
return packet.Id, nil
}
func (s *socket) loop() error {
defer func() {
s.LeaveAll()
p := packet{
Type: _DISCONNECT,
Id: -1,
}
s.socketHandler.onPacket(nil, &p)
}()
p := packet{
Type: _CONNECT,
Id: -1,
}
encoder := newEncoder(s.conn)
if err := encoder.Encode(p); err != nil {
return err
}
s.socketHandler.onPacket(nil, &p)
for {
decoder := newDecoder(s.conn)
var p packet
if err := decoder.Decode(&p); err != nil {
return err
}
ret, err := s.socketHandler.onPacket(decoder, &p)
if err != nil {
return err
}
switch p.Type {
case _CONNECT:
s.namespace = p.NSP
s.sendConnect()
case _BINARY_EVENT:
fallthrough
case _EVENT:
if p.Id >= 0 {
p := packet{
Type: _ACK,
Id: p.Id,
NSP: s.namespace,
Data: ret,
}
encoder := newEncoder(s.conn)
if err := encoder.Encode(p); err != nil {
return err
}
}
case _DISCONNECT:
return nil
}
}
}

View File

@ -0,0 +1,45 @@
package socketio
import (
"bytes"
"io"
)
type trimWriter struct {
trimChars string
trimBuf []byte
output io.Writer
}
func newTrimWriter(w io.Writer, trimChars string) *trimWriter {
return &trimWriter{
trimChars: trimChars,
output: w,
}
}
func (w *trimWriter) Write(p []byte) (int, error) {
out := bytes.TrimRight(p, w.trimChars)
buf := p[len(out):]
var written int
if (len(out) > 0) && (w.trimBuf != nil) {
var err error
if written, err = w.output.Write(w.trimBuf); err != nil {
return 0, err
}
w.trimBuf = nil
}
if w.trimBuf != nil {
w.trimBuf = append(w.trimBuf, buf...)
} else {
w.trimBuf = buf
}
if len(p) == 0 {
return written, nil
}
ret, err := w.output.Write(out)
if err != nil {
return 0, err
}
return written + ret, nil
}

View File

@ -0,0 +1,64 @@
package socketio
import (
"bytes"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestTrimWriter(t *testing.T) {
var inputs []string
var target string
var trim string
test := func() {
buf := bytes.NewBuffer(nil)
w := newTrimWriter(buf, trim)
for _, str := range inputs {
_, err := w.Write([]byte(str))
So(err, ShouldBeNil)
}
So(buf.String(), ShouldEqual, target)
}
Convey("Trim nothing", t, func() {
inputs = []string{"1234", "2234"}
target = "12342234"
trim = ""
test()
})
Convey("Trim something", t, func() {
trim = "34"
Convey("Something at the end of final packet", func() {
inputs = []string{"1234", "2234"}
target = "123422"
test()
})
Convey("Something at the multiple packets", func() {
inputs = []string{"1234", "3434"}
target = "12"
test()
})
Convey("Something in the middle of packets", func() {
inputs = []string{"1234", "3434", "5678"}
target = "123434345678"
test()
})
Convey("Something in the middle and end of packets", func() {
inputs = []string{"1234", "3434", "5678", "9034"}
target = "12343434567890"
test()
})
})
}

25
vendor/github.com/gorilla/websocket/.gitignore generated vendored Normal file
View File

@ -0,0 +1,25 @@
# 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
.idea/
*.iml

19
vendor/github.com/gorilla/websocket/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
sudo: false
matrix:
include:
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: 1.8
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

8
vendor/github.com/gorilla/websocket/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,8 @@
# This is the official list of Gorilla WebSocket authors for copyright
# purposes.
#
# Please keep the list sorted.
Gary Burd <gary@beagledreams.com>
Joachim Bauch <mail@joachim-bauch.de>

22
vendor/github.com/gorilla/websocket/LICENSE generated vendored Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

64
vendor/github.com/gorilla/websocket/README.md generated vendored Normal file
View File

@ -0,0 +1,64 @@
# Gorilla WebSocket
Gorilla WebSocket is a [Go](http://golang.org/) implementation of the
[WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol.
[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket)
[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket)
### Documentation
* [API Reference](http://godoc.org/github.com/gorilla/websocket)
* [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat)
* [Command example](https://github.com/gorilla/websocket/tree/master/examples/command)
* [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo)
* [File watch example](https://github.com/gorilla/websocket/tree/master/examples/filewatch)
### Status
The Gorilla WebSocket package provides a complete and tested implementation of
the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. The
package API is stable.
### Installation
go get github.com/gorilla/websocket
### Protocol Compliance
The Gorilla WebSocket package passes the server tests in the [Autobahn Test
Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn
subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn).
### Gorilla WebSocket compared with other packages
<table>
<tr>
<th></th>
<th><a href="http://godoc.org/github.com/gorilla/websocket">github.com/gorilla</a></th>
<th><a href="http://godoc.org/golang.org/x/net/websocket">golang.org/x/net</a></th>
</tr>
<tr>
<tr><td colspan="3"><a href="http://tools.ietf.org/html/rfc6455">RFC 6455</a> Features</td></tr>
<tr><td>Passes <a href="http://autobahn.ws/testsuite/">Autobahn Test Suite</a></td><td><a href="https://github.com/gorilla/websocket/tree/master/examples/autobahn">Yes</a></td><td>No</td></tr>
<tr><td>Receive <a href="https://tools.ietf.org/html/rfc6455#section-5.4">fragmented</a> message<td>Yes</td><td><a href="https://code.google.com/p/go/issues/detail?id=7632">No</a>, see note 1</td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.1">close</a> message</td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td><a href="https://code.google.com/p/go/issues/detail?id=4588">No</a></td></tr>
<tr><td>Send <a href="https://tools.ietf.org/html/rfc6455#section-5.5.2">pings</a> and receive <a href="https://tools.ietf.org/html/rfc6455#section-5.5.3">pongs</a></td><td><a href="http://godoc.org/github.com/gorilla/websocket#hdr-Control_Messages">Yes</a></td><td>No</td></tr>
<tr><td>Get the <a href="https://tools.ietf.org/html/rfc6455#section-5.6">type</a> of a received data message</td><td>Yes</td><td>Yes, see note 2</td></tr>
<tr><td colspan="3">Other Features</tr></td>
<tr><td><a href="https://tools.ietf.org/html/rfc7692">Compression Extensions</a></td><td>Experimental</td><td>No</td></tr>
<tr><td>Read message using io.Reader</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextReader">Yes</a></td><td>No, see note 3</td></tr>
<tr><td>Write message using io.WriteCloser</td><td><a href="http://godoc.org/github.com/gorilla/websocket#Conn.NextWriter">Yes</a></td><td>No, see note 3</td></tr>
</table>
Notes:
1. Large messages are fragmented in [Chrome's new WebSocket implementation](http://www.ietf.org/mail-archive/web/hybi/current/msg10503.html).
2. The application can get the type of a received data message by implementing
a [Codec marshal](http://godoc.org/golang.org/x/net/websocket#Codec.Marshal)
function.
3. The go.net io.Reader and io.Writer operate across WebSocket frame boundaries.
Read returns when the input buffer is full or a frame boundary is
encountered. Each call to Write sends a single frame message. The Gorilla
io.Reader and io.WriteCloser operate on a single WebSocket message.

Some files were not shown because too many files have changed in this diff Show More