Compare commits

..

8 Commits

Author SHA1 Message Date
f8bcfe447c
hid: clean up/finalize logging 2018-08-06 20:47:33 +02:00
fb648c37be
hid: move off of the log package
We don't spam with useless messages without -debug any longer.
2018-08-06 19:52:39 +02:00
bb0113021a
hid: port logging facilities
Though the regular mode now has timestamps and a new mode for systemd
has been added.
2018-08-06 19:49:06 +02:00
5a40d7c2ed
hid: cleanups
No functional changes.
2018-08-06 12:31:31 +02:00
f32e2f1483
hid: port IRC tests from liberty, fix tag parsing 2018-08-06 12:09:18 +02:00
62418ebb54
hid: rename connCloseWrite to connCloseWriter 2018-08-06 12:06:42 +02:00
6b45865e3d
hid: add the first tests
This has actually revealed a problem in the SSL 2.0 detection.
2018-08-06 12:06:20 +02:00
198cb87036
README: fix typos, add some references 2018-08-06 11:10:35 +02:00
3 changed files with 347 additions and 72 deletions

14
README
View File

@ -108,7 +108,7 @@ The c2 wiki unsurprisingly has a lot of material around the design and
realisation of GUIs, which might be useful. realisation of GUIs, which might be useful.
It seems like an aligning/constraint-based "layout manager" will be one of the It seems like an aligning/constraint-based "layout manager" will be one of the
first harder problems here. However I certainly don't want to used fixed first harder problems here. However I certainly don't want to use fixed
coordinates as they would introduce problems with different fonts and i18n. coordinates as they would introduce problems with different fonts and i18n.
We could use BDF fonts from the X11 distribution, but draw2d has native support We could use BDF fonts from the X11 distribution, but draw2d has native support
@ -117,13 +117,18 @@ for FreeType fonts and it's more of a choice between vectors and bitmaps.
The looks will be heavily inspired by Haiku and Windows 2000 and the user will The looks will be heavily inspired by Haiku and Windows 2000 and the user will
have no say in this, for simplicity. have no say in this, for simplicity.
Resources:
- https://github.com/golang/exp/tree/master/shiny is a GUI library
- https://github.com/as/shiny is a fork of it
- http://man.cat-v.org/plan_9/1/rio has a particular, unusual model
Internationalisation Internationalisation
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
For i18n https://github.com/leonelquinteros/gotext could be used, however I'll For i18n https://github.com/leonelquinteros/gotext could be used, however I'll
probably give up on this issue as I'm fine enough with English. probably give up on this issue as I'm fine enough with English.
Go also has x/text packages for this purpose, which might be better than GNU, Go also has x/text packages for this purpose, which might be better than GNU,
but is essentially still in development. but they're essentially still in development.
Versioning Versioning
~~~~~~~~~~ ~~~~~~~~~~
@ -191,7 +196,7 @@ takes with my 10 dictionaries isn't particularly bad), pack everything with
archive/zip. archive/zip.
Instead of ICU we may use x/text/collate and that's about everything we need. Instead of ICU we may use x/text/collate and that's about everything we need.
Since we have our own format, we may expect the indexed to be ordered by the Since we have our own format, we may expect the index to be ordered by the
locale's rules, assuming they don't change between versions. locale's rules, assuming they don't change between versions.
hmpc -- MPD client hmpc -- MPD client
@ -220,6 +225,9 @@ The real model for the editor is Qt Creator with FakeVIM, though this is not to
be a clone of it, e.g. the various "Output" lists could be just special buffers, be a clone of it, e.g. the various "Output" lists could be just special buffers,
which may be have names starting on "// ". which may be have names starting on "// ".
Resources:
- http://doc.cat-v.org/plan_9/4th_edition/papers/sam/
hfm -- file manager hfm -- file manager
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
All we need to achieve here is replace Midnight Commander, which besides the All we need to achieve here is replace Midnight Commander, which besides the

View File

@ -27,7 +27,7 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log" "log/syslog"
"net" "net"
"os" "os"
"os/signal" "os/signal"
@ -48,6 +48,100 @@ const (
projectVersion = "0" projectVersion = "0"
) )
// --- Logging -----------------------------------------------------------------
type logPrio int
const (
prioFatal logPrio = iota
prioError
prioWarning
prioStatus
prioDebug
)
func (lp logPrio) prefix() string {
switch lp {
case prioFatal:
return "fatal: "
case prioError:
return "error: "
case prioWarning:
return "warning: "
case prioStatus:
return ""
case prioDebug:
return "debug: "
default:
panic("unhandled log priority")
}
}
func (lp logPrio) syslogPrio() syslog.Priority {
switch lp {
case prioFatal:
return syslog.LOG_ERR
case prioError:
return syslog.LOG_ERR
case prioWarning:
return syslog.LOG_WARNING
case prioStatus:
return syslog.LOG_INFO
case prioDebug:
return syslog.LOG_DEBUG
default:
panic("unhandled log priority")
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
func logMessageStdio(prio logPrio, format string, args ...interface{}) {
// TODO: isatty-enabled colors based on prio.
os.Stderr.WriteString(time.Now().Format("2006-01-02 15:04:05 ") +
prio.prefix() + fmt.Sprintf(format, args...) + "\n")
}
func logMessageSystemd(prio logPrio, format string, args ...interface{}) {
if prio == prioFatal {
// There is no corresponding syslog severity.
format = "fatal: " + format
}
fmt.Fprintf(os.Stderr, "<%d>%s\n",
prio.syslogPrio(), fmt.Sprintf(format, args...))
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var logMessage = logMessageStdio
func printDebug(format string, args ...interface{}) {
if debugMode {
logMessage(prioDebug, format, args...)
}
}
func printStatus(format string, args ...interface{}) {
logMessage(prioStatus, format, args...)
}
func printWarning(format string, args ...interface{}) {
logMessage(prioWarning, format, args...)
}
func printError(format string, args ...interface{}) {
logMessage(prioError, format, args...)
}
// "fatal" is reserved for failures that would harm further operation.
func printFatal(format string, args ...interface{}) {
logMessage(prioFatal, format, args...)
}
func exitFatal(format string, args ...interface{}) {
printFatal(format, args...)
os.Exit(1)
}
// --- Utilities --------------------------------------------------------------- // --- Utilities ---------------------------------------------------------------
// Split a string by a set of UTF-8 delimiters, optionally ignoring empty items. // Split a string by a set of UTF-8 delimiters, optionally ignoring empty items.
@ -164,9 +258,7 @@ func findTildeHome(username string) string {
} else if v, ok := os.LookupEnv("HOME"); ok { } else if v, ok := os.LookupEnv("HOME"); ok {
return v return v
} }
if debugMode { printDebug("failed to expand the home directory for %s", username)
log.Printf("failed to expand the home directory for %s", username)
}
return "~" + username return "~" + username
} }
@ -192,27 +284,30 @@ func resolveFilename(filename string, relativeCB func(string) string) string {
// --- Simple file I/O --------------------------------------------------------- // --- Simple file I/O ---------------------------------------------------------
// Overwrites filename contents with data; creates directories as needed. // Overwrites filename contents with data; creates directories as needed.
func writeFile(filename string, data []byte) error { func writeFile(path string, data []byte) error {
if dir := filepath.Dir(filename); dir != "." { if dir := filepath.Dir(path); dir != "." {
if err := os.MkdirAll(dir, 0755); err != nil { if err := os.MkdirAll(dir, 0755); err != nil {
return err return err
} }
} }
return ioutil.WriteFile(filename, data, 0644) return ioutil.WriteFile(path, data, 0644)
} }
// Wrapper for writeFile that makes sure that the new data has been written // Wrapper for writeFile that makes sure that the new data has been written
// to disk in its entirety before overriding the old file. // to disk in its entirety before overriding the old file.
func writeFileSafe(filename string, data []byte) error { func writeFileSafe(path string, data []byte) error {
temp := filename + ".new" temp := path + ".new"
if err := writeFile(temp, data); err != nil { if err := writeFile(temp, data); err != nil {
return err return err
} }
return os.Rename(temp, filename) return os.Rename(temp, path)
} }
// --- Simple configuration ---------------------------------------------------- // --- Simple configuration ----------------------------------------------------
// This is the bare minimum to make an application configurable.
// Keys are stripped of surrounding whitespace, values are not.
type simpleConfigItem struct { type simpleConfigItem struct {
key string // INI key key string // INI key
def string // default value def string // default value
@ -229,8 +324,8 @@ func (sc simpleConfig) loadDefaults(table []simpleConfigItem) {
func (sc simpleConfig) updateFromFile() error { func (sc simpleConfig) updateFromFile() error {
basename := projectName + ".conf" basename := projectName + ".conf"
filename := resolveFilename(basename, resolveRelativeConfigFilename) path := resolveFilename(basename, resolveRelativeConfigFilename)
if filename == "" { if path == "" {
return &os.PathError{ return &os.PathError{
Op: "cannot find", Op: "cannot find",
Path: basename, Path: basename,
@ -238,7 +333,7 @@ func (sc simpleConfig) updateFromFile() error {
} }
} }
f, err := os.Open(filename) f, err := os.Open(path)
if err != nil { if err != nil {
return err return err
} }
@ -253,7 +348,7 @@ func (sc simpleConfig) updateFromFile() error {
equals := strings.IndexByte(line, '=') equals := strings.IndexByte(line, '=')
if equals <= 0 { if equals <= 0 {
return fmt.Errorf("%s:%d: malformed line", filename, lineNo) return fmt.Errorf("%s:%d: malformed line", path, lineNo)
} }
sc[strings.TrimRight(line[:equals], " \t")] = line[equals+1:] sc[strings.TrimRight(line[:equals], " \t")] = line[equals+1:]
@ -295,13 +390,13 @@ func callSimpleConfigWriteDefault(pathHint string, table []simpleConfigItem) {
``, ``,
} }
filename, err := simpleConfigWriteDefault( path, err := simpleConfigWriteDefault(
pathHint, strings.Join(prologLines, "\n"), table) pathHint, strings.Join(prologLines, "\n"), table)
if err != nil { if err != nil {
log.Fatalln(err) exitFatal("%s", err)
} }
log.Printf("configuration written to `%s'\n", filename) printStatus("configuration written to `%s'", path)
} }
// --- Configuration ----------------------------------------------------------- // --- Configuration -----------------------------------------------------------
@ -420,7 +515,7 @@ func ircFnmatch(pattern string, s string) bool {
} }
var reMsg = regexp.MustCompile( var reMsg = regexp.MustCompile(
`^(@[^ ]* +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`) `^(?:@([^ ]*) +)?(?::([^! ]*)(?:!([^@]*)@([^ ]*))? +)?([^ ]+)(.*)?$`)
var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`) var reArgs = regexp.MustCompile(`:.*| [^: ][^ ]*`)
type message struct { type message struct {
@ -563,7 +658,7 @@ func ircIsValidFingerprint(fp string) bool {
// --- Clients (equals users) -------------------------------------------------- // --- Clients (equals users) --------------------------------------------------
type connCloseWrite interface { type connCloseWriter interface {
net.Conn net.Conn
CloseWrite() error CloseWrite() error
} }
@ -587,15 +682,15 @@ const (
) )
type client struct { type client struct {
transport net.Conn // underlying connection transport net.Conn // underlying connection
tls *tls.Conn // TLS, if detected tls *tls.Conn // TLS, if detected
conn connCloseWrite // high-level connection conn connCloseWriter // high-level connection
recvQ []byte // unprocessed input recvQ []byte // unprocessed input
sendQ []byte // unprocessed output sendQ []byte // unprocessed output
reading bool // whether a reading goroutine is running reading bool // whether a reading goroutine is running
writing bool // whether a writing goroutine is running writing bool // whether a writing goroutine is running
closing bool // whether we're closing the connection closing bool // whether we're closing the connection
killTimer *time.Timer // hard kill timeout killTimer *time.Timer // hard kill timeout
opened time.Time // when the connection was opened opened time.Time // when the connection was opened
nSentMessages uint // number of sent messages total nSentMessages uint // number of sent messages total
@ -793,10 +888,10 @@ var (
// Forcefully tear down all connections. // Forcefully tear down all connections.
func forceQuit(reason string) { func forceQuit(reason string) {
if !quitting { if !quitting {
log.Fatalln("forceQuit called without initiateQuit") exitFatal("forceQuit called without initiateQuit")
} }
log.Printf("forced shutdown (%s)\n", reason) printStatus("forced shutdown (%s)", reason)
for c := range clients { for c := range clients {
// initiateQuit has already unregistered the client. // initiateQuit has already unregistered the client.
c.kill("Shutting down") c.kill("Shutting down")
@ -805,10 +900,10 @@ func forceQuit(reason string) {
// Initiate a clean shutdown of the whole daemon. // Initiate a clean shutdown of the whole daemon.
func initiateQuit() { func initiateQuit() {
log.Println("shutting down") printStatus("shutting down")
for _, ln := range listeners { for _, ln := range listeners {
if err := ln.Close(); err != nil { if err := ln.Close(); err != nil {
log.Println(err) printError("%s", err)
} }
} }
for c := range clients { for c := range clients {
@ -857,6 +952,12 @@ func ircNotifyRoommates(c *client, message string) {
// --- Clients (continued) ----------------------------------------------------- // --- Clients (continued) -----------------------------------------------------
func (c *client) printDebug(format string, args ...interface{}) {
if debugMode {
printDebug("(%s) %s", c.address, fmt.Sprintf(format, args...))
}
}
func ircAppendClientModes(m uint, mode []byte) []byte { func ircAppendClientModes(m uint, mode []byte) []byte {
if 0 != m&ircUserModeInvisible { if 0 != m&ircUserModeInvisible {
mode = append(mode, 'i') mode = append(mode, 'i')
@ -903,6 +1004,8 @@ func (c *client) send(line string) {
bytes = bytes[:ircMaxMessageLength] bytes = bytes[:ircMaxMessageLength]
} }
c.printDebug("<- %s", bytes)
// TODO: Kill the connection above some "SendQ" threshold (careful!) // TODO: Kill the connection above some "SendQ" threshold (careful!)
c.sendQ = append(c.sendQ, bytes...) c.sendQ = append(c.sendQ, bytes...)
c.sendQ = append(c.sendQ, "\r\n"...) c.sendQ = append(c.sendQ, "\r\n"...)
@ -954,12 +1057,12 @@ func (c *client) unregister(reason string) {
// Close the connection and forget about the client. // Close the connection and forget about the client.
func (c *client) kill(reason string) { func (c *client) kill(reason string) {
if reason == "" { if reason == "" {
reason = "Client exited" c.unregister("Client exited")
} else {
c.unregister(reason)
} }
c.unregister(reason)
// TODO: Log the address; seems like we always have c.address. c.printDebug("client destroyed (%s)", reason)
log.Println("client destroyed")
// Try to send a "close notify" alert if the TLS object is ready, // Try to send a "close notify" alert if the TLS object is ready,
// otherwise just tear down the transport. // otherwise just tear down the transport.
@ -2852,14 +2955,14 @@ func ircProcessMessage(c *client, msg *message, raw string) {
// Handle the results from initializing the client's connection. // Handle the results from initializing the client's connection.
func (c *client) onPrepared(host string, isTLS bool) { func (c *client) onPrepared(host string, isTLS bool) {
c.printDebug("client resolved to %s, TLS %t", host, isTLS)
if !isTLS { if !isTLS {
c.conn = c.transport.(connCloseWrite) c.conn = c.transport.(connCloseWriter)
} else if tlsConf != nil { } else if tlsConf != nil {
c.tls = tls.Server(c.transport, tlsConf) c.tls = tls.Server(c.transport, tlsConf)
c.conn = c.tls c.conn = c.tls
} else { } else {
log.Printf("could not initialize TLS for %s: TLS support disabled\n", c.printDebug("could not initialize TLS: disabled")
c.address)
c.kill("TLS support disabled") c.kill("TLS support disabled")
return return
} }
@ -2892,10 +2995,10 @@ func (c *client) onRead(data []byte, readErr error) {
// XXX: And since it accepts LF, we miscalculate receivedBytes within. // XXX: And since it accepts LF, we miscalculate receivedBytes within.
c.recvQ = c.recvQ[advance:] c.recvQ = c.recvQ[advance:]
line := string(token) line := string(token)
log.Printf("-> %s\n", line) c.printDebug("-> %s", line)
if msg := ircParseMessage(line); msg == nil { if msg := ircParseMessage(line); msg == nil {
log.Println("error: invalid line") c.printDebug("error: invalid line")
} else { } else {
ircProcessMessage(c, msg, line) ircProcessMessage(c, msg, line)
} }
@ -2905,18 +3008,17 @@ func (c *client) onRead(data []byte, readErr error) {
c.reading = false c.reading = false
if readErr != io.EOF { if readErr != io.EOF {
log.Println(readErr) c.printDebug("%s", readErr)
c.kill(readErr.Error()) c.kill(readErr.Error())
} else if c.closing { } else if c.closing {
// Disregarding whether a clean shutdown has happened or not. // Disregarding whether a clean shutdown has happened or not.
log.Println("client finished shutdown") c.printDebug("client finished shutdown")
c.kill("TODO") c.kill("")
} else { } else {
log.Println("client EOF") c.printDebug("client EOF")
c.closeLink("") c.closeLink("")
} }
} else if len(c.recvQ) > 8192 { } else if len(c.recvQ) > 8192 {
log.Println("client recvQ overrun")
c.closeLink("recvQ overrun") c.closeLink("recvQ overrun")
// tls.Conn doesn't have the CloseRead method (and it needs to be able // tls.Conn doesn't have the CloseRead method (and it needs to be able
@ -2941,7 +3043,7 @@ func (c *client) onWrite(written int, writeErr error) {
c.writing = false c.writing = false
if writeErr != nil { if writeErr != nil {
log.Println(writeErr) c.printDebug("%s", writeErr)
c.kill(writeErr.Error()) c.kill(writeErr.Error())
} else if len(c.sendQ) > 0 { } else if len(c.sendQ) > 0 {
c.flushSendQ() c.flushSendQ()
@ -2949,7 +3051,7 @@ func (c *client) onWrite(written int, writeErr error) {
if c.reading { if c.reading {
c.conn.CloseWrite() c.conn.CloseWrite()
} else { } else {
c.kill("TODO") c.kill("")
} }
} }
} }
@ -2968,9 +3070,9 @@ func accept(ln net.Listener) {
return return
} }
if op, ok := err.(net.Error); !ok || !op.Temporary() { if op, ok := err.(net.Error); !ok || !op.Temporary() {
log.Fatalln(err) exitFatal("%s", err)
} else { } else {
log.Println(err) printError("%s", err)
} }
} else { } else {
// TCP_NODELAY is set by default on TCPConns. // TCP_NODELAY is set by default on TCPConns.
@ -2980,12 +3082,7 @@ func accept(ln net.Listener) {
} }
func prepare(client *client) { func prepare(client *client) {
conn := client.transport conn, host := client.transport, client.hostname
host, _, err := net.SplitHostPort(conn.RemoteAddr().String())
if err != nil {
// In effect, we require TCP/UDP, as they have port numbers.
log.Fatalln(err)
}
// The Cgo resolver doesn't pthread_cancel getnameinfo threads, so not // The Cgo resolver doesn't pthread_cancel getnameinfo threads, so not
// bothering with pointless contexts. // bothering with pointless contexts.
@ -2993,7 +3090,7 @@ func prepare(client *client) {
go func() { go func() {
defer close(ch) defer close(ch)
if names, err := net.LookupAddr(host); err != nil { if names, err := net.LookupAddr(host); err != nil {
log.Println(err) printError("%s", err)
} else { } else {
ch <- names[0] ch <- names[0]
} }
@ -3013,7 +3110,7 @@ func prepare(client *client) {
isTLS := false isTLS := false
if sysconn, err := conn.(syscall.Conn).SyscallConn(); err != nil { if sysconn, err := conn.(syscall.Conn).SyscallConn(); err != nil {
// This is just for the TLS detection and doesn't need to be fatal. // This is just for the TLS detection and doesn't need to be fatal.
log.Println(err) printError("%s", err)
} else { } else {
isTLS = detectTLS(sysconn) isTLS = detectTLS(sysconn)
} }
@ -3062,18 +3159,16 @@ func processOneEvent() {
case conn := <-conns: case conn := <-conns:
if maxConnections > 0 && len(clients) >= maxConnections { if maxConnections > 0 && len(clients) >= maxConnections {
log.Println("connection limit reached, refusing connection") printDebug("connection limit reached, refusing connection")
conn.Close() conn.Close()
break break
} }
log.Println("accepted client connection")
// In effect, we require TCP/UDP, as they have port numbers.
address := conn.RemoteAddr().String() address := conn.RemoteAddr().String()
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
log.Fatalln(err) // In effect, we require TCP/UDP, as they have port numbers.
exitFatal("%s", err)
} }
c := &client{ c := &client{
@ -3087,26 +3182,25 @@ func processOneEvent() {
// TODO: Make this configurable and more fine-grained. // TODO: Make this configurable and more fine-grained.
antiflood: newFloodDetector(10*time.Second, 20), antiflood: newFloodDetector(10*time.Second, 20),
} }
clients[c] = true clients[c] = true
c.printDebug("new client")
go prepare(c) go prepare(c)
// The TLS autodetection in prepare needs to have a timeout. // The TLS autodetection in prepare needs to have a timeout.
c.setKillTimer() c.setKillTimer()
case ev := <-prepared: case ev := <-prepared:
log.Println("client is ready:", ev.host)
if _, ok := clients[ev.client]; ok { if _, ok := clients[ev.client]; ok {
ev.client.onPrepared(ev.host, ev.isTLS) ev.client.onPrepared(ev.host, ev.isTLS)
} }
case ev := <-reads: case ev := <-reads:
log.Println("received data from client")
if _, ok := clients[ev.client]; ok { if _, ok := clients[ev.client]; ok {
ev.client.onRead(ev.data, ev.err) ev.client.onRead(ev.data, ev.err)
} }
case ev := <-writes: case ev := <-writes:
log.Println("sent data to client")
if _, ok := clients[ev.client]; ok { if _, ok := clients[ev.client]; ok {
ev.client.onWrite(ev.written, ev.err) ev.client.onWrite(ev.written, ev.err)
} }
@ -3296,6 +3390,7 @@ func ircSetupListenFDs() error {
return err return err
} }
listeners = append(listeners, ln) listeners = append(listeners, ln)
printStatus("listening on %s", address)
} }
if len(listeners) == 0 { if len(listeners) == 0 {
return errors.New("network setup failed: no ports to listen on") return errors.New("network setup failed: no ports to listen on")
@ -3309,10 +3404,11 @@ func ircSetupListenFDs() error {
// --- Main -------------------------------------------------------------------- // --- Main --------------------------------------------------------------------
func main() { func main() {
flag.BoolVar(&debugMode, "debug", false, "run in debug mode") flag.BoolVar(&debugMode, "debug", false, "run in verbose debug mode")
version := flag.Bool("version", false, "show version and exit") version := flag.Bool("version", false, "show version and exit")
writeDefaultCfg := flag.Bool("writedefaultcfg", false, writeDefaultCfg := flag.Bool("writedefaultcfg", false,
"write a default configuration file and exit") "write a default configuration file and exit")
systemd := flag.Bool("systemd", false, "log in systemd format")
flag.Parse() flag.Parse()
@ -3324,6 +3420,9 @@ func main() {
callSimpleConfigWriteDefault("", configTable) callSimpleConfigWriteDefault("", configTable)
return return
} }
if *systemd {
logMessage = logMessageSystemd
}
if flag.NArg() > 0 { if flag.NArg() > 0 {
flag.Usage() flag.Usage()
os.Exit(2) os.Exit(2)
@ -3332,7 +3431,7 @@ func main() {
config = make(simpleConfig) config = make(simpleConfig)
config.loadDefaults(configTable) config.loadDefaults(configTable)
if err := config.updateFromFile(); err != nil && !os.IsNotExist(err) { if err := config.updateFromFile(); err != nil && !os.IsNotExist(err) {
log.Println("error loading configuration", err) printError("error loading configuration: %s", err)
os.Exit(1) os.Exit(1)
} }
@ -3348,7 +3447,7 @@ func main() {
ircSetupListenFDs, ircSetupListenFDs,
} { } {
if err := fn(); err != nil { if err := fn(); err != nil {
log.Fatalln(err) exitFatal("%s", err)
} }
} }

168
hid/main_test.go Normal file
View File

@ -0,0 +1,168 @@
//
// Copyright (c) 2015 - 2018, Přemysl Janouch <p@janouch.name>
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
package main
import (
"crypto/tls"
"net"
"os"
"reflect"
"syscall"
"testing"
)
func TestSplitString(t *testing.T) {
var splitStringTests = []struct {
s, delims string
ignoreEmpty bool
result []string
}{
{",a,,bc", ",", false, []string{"", "a", "", "bc"}},
{",a,,bc", ",", true, []string{"a", "bc"}},
{"a,;bc,", ",;", false, []string{"a", "", "bc", ""}},
{"a,;bc,", ",;", true, []string{"a", "bc"}},
{"", ",", false, []string{""}},
{"", ",", true, nil},
}
for i, d := range splitStringTests {
got := splitString(d.s, d.delims, d.ignoreEmpty)
if !reflect.DeepEqual(got, d.result) {
t.Errorf("case %d: %v should be %v\n", i, got, d.result)
}
}
}
func socketpair() (*os.File, *os.File, error) {
pair, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, nil, err
}
// See go #24331, this makes 1.11 use the internal poller
// while there wasn't a way to achieve that before.
if err := syscall.SetNonblock(int(pair[0]), true); err != nil {
return nil, nil, err
}
if err := syscall.SetNonblock(int(pair[1]), true); err != nil {
return nil, nil, err
}
fa := os.NewFile(uintptr(pair[0]), "a")
if fa == nil {
return nil, nil, os.ErrInvalid
}
fb := os.NewFile(uintptr(pair[1]), "b")
if fb == nil {
fa.Close()
return nil, nil, os.ErrInvalid
}
return fa, fb, nil
}
func TestDetectTLS(t *testing.T) {
detectTLSFromFunc := func(t *testing.T, writer func(net.Conn)) bool {
// net.Pipe doesn't use file descriptors, we need a socketpair.
sockA, sockB, err := socketpair()
if err != nil {
t.Fatal(err)
}
defer sockA.Close()
defer sockB.Close()
fcB, err := net.FileConn(sockB)
if err != nil {
t.Fatal(err)
}
go writer(fcB)
fcA, err := net.FileConn(sockA)
if err != nil {
t.Fatal(err)
}
sc, err := fcA.(syscall.Conn).SyscallConn()
if err != nil {
t.Fatal(err)
}
return detectTLS(sc)
}
t.Run("SSL_2.0", func(t *testing.T) {
if !detectTLSFromFunc(t, func(fc net.Conn) {
// The obsolete, useless, unsupported SSL 2.0 record format.
_, _ = fc.Write([]byte{0x80, 0x01, 0x01})
}) {
t.Error("could not detect SSL")
}
})
t.Run("crypto_tls", func(t *testing.T) {
if !detectTLSFromFunc(t, func(fc net.Conn) {
conn := tls.Client(fc, &tls.Config{InsecureSkipVerify: true})
_ = conn.Handshake()
}) {
t.Error("could not detect TLS")
}
})
t.Run("text", func(t *testing.T) {
if detectTLSFromFunc(t, func(fc net.Conn) {
_, _ = fc.Write([]byte("ПРЕВЕД"))
}) {
t.Error("detected UTF-8 as TLS")
}
})
t.Run("EOF", func(t *testing.T) {
type connCloseWriter interface {
net.Conn
CloseWrite() error
}
if detectTLSFromFunc(t, func(fc net.Conn) {
_ = fc.(connCloseWriter).CloseWrite()
}) {
t.Error("detected EOF as TLS")
}
})
}
func TestIRC(t *testing.T) {
msg := ircParseMessage(
`@first=a\:\s\r\n\\;2nd :srv hi there :good m8 :how are you?`)
if !reflect.DeepEqual(msg.tags, map[string]string{
"first": "a; \r\n\\",
"2nd": "",
}) {
t.Error("tags parsed incorrectly")
}
if msg.nick != "srv" || msg.user != "" || msg.host != "" {
t.Error("server name parsed incorrectly")
}
if msg.command != "hi" {
t.Error("command name parsed incorrectly")
}
if !reflect.DeepEqual(msg.params,
[]string{"there", "good m8 :how are you?"}) {
t.Error("params parsed incorrectly")
}
if !ircEqual("[fag]^", "{FAG}~") {
t.Error("string case comparison not according to RFC 2812")
}
// TODO: More tests.
}