27 Commits

Author SHA1 Message Date
4ea6cbb6f1 readme 2017-11-14 18:54:51 +13:00
b2ae91959e bump DEFAULT_CLIENT_VERSION to 0.15 2017-11-14 18:45:36 +13:00
f5fbce3ee6 add a fallback reconnection if no keepalives are recieved within ~24 hours 2017-11-14 18:45:06 +13:00
2cbc8f8496 fix regex matching protocol messages in the middle of multi-line public messages 2017-11-14 18:36:34 +13:00
5bf30ed95f update examples for StateChange member API change 2017-11-14 18:29:04 +13:00
a20fce3725 doc: add go-get tags to README 2017-10-28 12:32:06 +13:00
5c6435cf81 retag commits in vX.Y.Z format 2017-10-28 12:16:24 +13:00
b7dfcbc2f3 Added tag release-0.14 for changeset 6422ed687cd3 2017-02-11 13:56:27 +13:00
21847db9fd readme 2017-02-11 13:56:08 +13:00
b8122710f5 patch a crash on incomplete supplied UserIP2 2017-02-11 13:55:20 +13:00
f8c0fc54d8 Added tag release-0.13 for changeset 3ee0f4ea5142 2017-02-09 19:31:24 +13:00
4490444d1c readme 2017-02-09 19:31:19 +13:00
c5f655ff78 doc: update TODO 2017-02-09 19:28:58 +13:00
4415109174 support UserIP2 extension 2017-02-09 19:28:37 +13:00
0adc13ddfa move our is-connected one half roundtrip sooner 2017-02-09 19:19:17 +13:00
59c56fea1b doc: todo 2017-02-09 19:15:53 +13:00
7f8f98aa5a add ChatOnly to our $Supports 2017-02-08 19:30:53 +13:00
8536cb0a5c implement QuickList, save a network roundtrip on supported hubs (ptokax) 2017-02-08 19:22:07 +13:00
553ec20850 fix not emitting EVENT_USER_UPDATED_INFO on recieved MyINFO (!) 2017-02-08 18:58:14 +13:00
a403dac461 declare NoHello support (since our MyINFO handler will add unknown users) 2017-02-08 18:58:03 +13:00
59a37b975c more idiomatic error handling 2017-02-08 18:57:49 +13:00
a4e7d876cc Added tag release-0.12 for changeset 22b156a6fc2f 2017-02-05 22:37:24 +13:00
7c3069eabb readme 2017-02-05 22:35:18 +13:00
3cc31323ad more fine-grained locking around user map (fixes deadlock if callee uses Users() in response to processEvent) 2017-02-05 19:06:17 +13:00
7f898dff32 display a system message and disconnect if we're asked for a password but don't have one 2017-02-05 17:12:59 +13:00
58c1151278 readme for 0.11 2016-11-29 20:18:17 +13:00
d1c549f1f2 Added tag release-0.11 for changeset 5149ffe70ea8 2016-11-29 20:13:14 +13:00
7 changed files with 160 additions and 51 deletions

24
.hgtags
View File

@@ -1,15 +1,19 @@
945ab4b16d05aa084f71bf5da9a3f687e0ec8bbd libnmdc-r1
945ab4b16d05aa084f71bf5da9a3f687e0ec8bbd v0.1.0
02a360e95480b97ddad83add5db48b2766339a99 nmdc-log-service-1.0.0
137c1b65039e03c80379826a6efdfd808f6fbc8f libnmdc-r2
d8b64d5527c2a5e4d76872e5bc3d69f7646135c6 libnmdc-r3
137c1b65039e03c80379826a6efdfd808f6fbc8f v0.2.0
d8b64d5527c2a5e4d76872e5bc3d69f7646135c6 v0.3.0
fca41372e400853775b02e951f9db91d87f41adb nmdc-log-service-1.0.1
050b424a7c5d5a27c9323c8810f3afbead1f5b96 libnmdc-r4
050b424a7c5d5a27c9323c8810f3afbead1f5b96 v0.4.0
da9f123633f9c28be6435ed7898139665d4c39d9 nmdc-log-service-1.0.2
75a78f6a78f249a2cd8aa3d29f7e5e6319b4e03b libnmdc-r5
75a78f6a78f249a2cd8aa3d29f7e5e6319b4e03b v0.5.0
4116422bb10229d887f9296970a166fa1ef8c5fd nmdc-log-service-1.0.3
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 nmdc-log-service-1.0.4
cb86f3a40115cc46f450c0c83fd9b9d3b740e820 libnmdc-r6
71343a2c641a438206d30ea7e75dc89a11dbef00 libnmdc-r7
b0e57a5fcffdf4102d669db51a3648ddf66a0792 libnmdc-r8
e7c2c71ef24b386add728fad35fff4a996fccbac libnmdc-r9
3ecc037cf2d7080572fe87c2e39ecd153fb0e947 libnmdc-r10
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

View File

@@ -15,7 +15,7 @@ func ExampleHubConnectionOptions_Connect() {
event := <-hub.OnEvent
switch event.EventType {
case EVENT_CONNECTION_STATE_CHANGED:
fmt.Printf("Connection -- %s (%s)\n", event.StateChange.Format(), event.Message)
fmt.Printf("Connection -- %s (%s)\n", event.StateChange, event.Message)
case EVENT_PUBLIC:
fmt.Printf("Message from '%s': '%s'\n", event.Nick, event.Message)
@@ -34,7 +34,7 @@ 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.Format(), event.Message)
fmt.Printf("Connection -- %s (%s)\n", event.StateChange, event.Message)
case EVENT_PUBLIC:
fmt.Printf("Message from '%s': '%s'\n", event.Nick, event.Message)

View File

@@ -2,6 +2,7 @@ package libnmdc
import (
"crypto/tls"
"fmt"
"net"
"strconv"
"strings"
@@ -28,6 +29,9 @@ type HubConnection struct {
connValid bool
sentOurHello bool
autoReconnect bool
lastDataRecieved time.Time
supports map[string]struct{}
}
// Thread-safe user accessor.
@@ -67,10 +71,11 @@ func (this *HubConnection) UserCount() int {
func (this *HubConnection) userJoined_NameOnly(nick string) {
if !this.UserExists(nick) {
this.userLock.Lock()
defer this.userLock.Unlock()
this.userLock.Lock()
this.users[nick] = *NewUserInfo(nick)
this.userLock.Unlock() // Don't lock over a processEvent boundary
this.processEvent(HubEvent{EventType: EVENT_USER_JOINED, Nick: nick})
}
}
@@ -78,14 +83,14 @@ func (this *HubConnection) userJoined_NameOnly(nick string) {
func (this *HubConnection) userJoined_Full(uinf *UserInfo) {
// n.b. also called when we get a replacement MyINFO for someone
this.userLock.Lock()
defer this.userLock.Unlock()
_, userExisted := this.users[uinf.Nick] // don't use UserExists as it would deadlock the mutex
this.users[uinf.Nick] = *uinf
this.userLock.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})
}
}
@@ -132,9 +137,8 @@ func (this *HubConnection) processProtocolMessage(message string) {
switch commandParts[0] {
case "$Lock":
this.SayRaw("$Supports NoGetINFO UserCommand UserIP2|" +
"$Key " + unlock([]byte(commandParts[1])) + "|" +
"$ValidateNick " + Escape(this.Hco.Self.Nick) + "|")
this.SayRaw("$Supports NoHello NoGetINFO UserCommand UserIP2 QuickList ChatOnly|" +
"$Key " + unlock([]byte(commandParts[1])) + "|")
this.sentOurHello = false
case "$Hello":
@@ -167,26 +171,32 @@ func (this *HubConnection) processProtocolMessage(message string) {
this.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "Incorrect password."})
case "$GetPass":
if len(this.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.processEvent(HubEvent{EventType: EVENT_SYSTEM_MESSAGE_FROM_CONN, Message: "This account is passworded."})
this.Disconnect()
} else {
this.SayRaw("$MyPass " + Escape(this.Hco.NickPassword) + "|")
}
case "$Quit":
func() {
this.userLock.Lock()
defer this.userLock.Unlock()
delete(this.users, commandParts[1])
}()
this.userLock.Unlock() // Don't lock over a processEvent boundary
this.processEvent(HubEvent{EventType: EVENT_USER_PART, Nick: commandParts[1]})
case "$MyINFO":
u := UserInfo{}
err := u.fromMyINFO(commandParts[1])
if err == nil {
this.userJoined_Full(&u)
} else {
if err != nil {
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: err.Error()})
return
}
this.userJoined_Full(&u)
case "$NickList":
nicklist := strings.Split(commandParts[1], "$$")
for _, nick := range nicklist {
@@ -237,11 +247,39 @@ func (this *HubConnection) processProtocolMessage(message string) {
}
case "$UserIP":
// Final message in PtokaX connection handshake - trigger connection callback.
// This might not be the case for other hubsofts, though
if this.State != CONNECTIONSTATE_CONNECTED {
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.State = CONNECTIONSTATE_CONNECTED
this.userLock.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.users[ip2nick]
if !ok {
this.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.users[ip2nick] = uinfo
}
}
this.userLock.Unlock()
for _, nick := range notifyOfUpdate {
this.processEvent(HubEvent{EventType: EVENT_USER_UPDATED_INFO, Nick: nick})
}
case "$ForceMove":
@@ -269,8 +307,34 @@ func (this *HubConnection) processProtocolMessage(message string) {
this.processEvent(HubEvent{EventType: EVENT_DEBUG_MESSAGE, Message: "Malformed usercommand '" + commandParts[1] + "'"})
}
// IGNORABLE COMMANDS
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.SayRaw("$GetNickList|")
} else {
this.SayRaw("$ValidateNick " + Escape(this.Hco.Self.Nick) + "|")
}
// This also counts as the end of the handshake from our POV. Consider
// ourselves logged in
this.sentOurHello = true
if this.State != CONNECTIONSTATE_CONNECTED {
this.processEvent(HubEvent{EventType: EVENT_CONNECTION_STATE_CHANGED, StateChange: CONNECTIONSTATE_CONNECTED})
this.State = CONNECTIONSTATE_CONNECTED
}
}
// IGNORABLE COMMANDS
case "$HubTopic":
case "$Search":
case "$ConnectToMe":
@@ -336,6 +400,7 @@ func (this *HubConnection) worker() {
}
if nbytes > 0 {
this.lastDataRecieved = time.Now()
fullBuffer += string(readBuff[0:nbytes])
}
}
@@ -354,6 +419,10 @@ func (this *HubConnection) worker() {
}
}
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

1
TODO.txt Normal file
View File

@@ -0,0 +1 @@
- Implement ZPipe ($ZOn)

View File

@@ -24,6 +24,7 @@ type UserInfo struct {
HubsRegistered uint64
HubsOperator uint64
IsOperator bool
IPAddress string
}
var rx_myinfo *regexp.Regexp

View File

@@ -1,14 +1,44 @@
An NMDC client protocol library for Golang.
- Offers both channel-based and synchronous APIs.
- Includes a sample logging client using the channel-based API.
- This code hosting site isn't (yet) compatible with `go get`.
This package offers both channel-based and synchronous APIs. Example is included.
Written in golang
Tags: nmdc
=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-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`

View File

@@ -10,10 +10,11 @@ import (
const (
DEFAULT_CLIENT_TAG string = "libnmdc.go"
DEFAULT_CLIENT_VERSION string = "0.11"
DEFAULT_CLIENT_VERSION string = "0.15"
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
)
var rx_protocolMessage *regexp.Regexp
@@ -23,10 +24,13 @@ var rx_userCommand *regexp.Regexp
var ErrNotConnected error = errors.New("Not connected")
func init() {
rx_protocolMessage = regexp.MustCompile("(?ms)^[^|]*\\|")
rx_publicChat = regexp.MustCompile("(?ms)^<([^>]*)> (.*)$")
rx_incomingTo = regexp.MustCompile("(?ms)^([^ ]+) From: ([^ ]+) \\$<([^>]*)> (.*)")
rx_userCommand = regexp.MustCompile(`(?ms)^(\d+) (\d+)\s?([^\$]*)\$?(.*)`)
// 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
rx_protocolMessage = regexp.MustCompile(`(?ms)\A[^|]*\|`)
rx_publicChat = regexp.MustCompile(`(?ms)\A<([^>]*)> (.*)$`)
rx_incomingTo = regexp.MustCompile(`(?ms)\A([^ ]+) From: ([^ ]+) \$<([^>]*)> (.*)`)
rx_userCommand = regexp.MustCompile(`(?ms)\A(\d+) (\d+)\s?([^\$]*)\$?(.*)`)
}
func Unescape(encoded string) string {