Author | SHA1 | Message | Date |
---|---|---|---|
Maciek Borzecki | 37e45ea847 |
go mod support
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | d76c6e3397 |
cmd/mconnect-connector: trivial packet receive/send loop
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | 34f26d176a |
protocol: update connection to TLS after sending identity
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | f2f4245ca8 |
protocol: send/receive helpers
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | 2cb0eb8639 |
protocol: certificate generator
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | c40ed84737 |
protocol: starting with listener
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | bd1497d632 |
protocol/packet: pair packet wrappers
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
5 years ago |
Maciek Borzecki | 1c0400f044 |
cmd/mconnect-connector: helper tool for establishing connections
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | aedd1fa243 |
protocol: connection helper
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | b675280994 |
cmd/mconnect-discover: display tcp port
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | b447fdfb7d |
protocol/packet: add packet encoder and decoder
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | c5923774f4 |
protocol/packet: helper for creating identity packets
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | ad36298f60 |
cmd/mconnect-discover: add flags
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | c9b15623b9 |
utils/flags: some helpers for github.com/jessevdk/go-flags
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | b482b5d2a9 |
cmd/mconnect-discover: track discovered devices, nicer output
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 322fb34aea |
logger: add level support
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 2486560099 |
discovery: use protocol port wrappers
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | c6f33b9e4f |
protocol: add UDP and TCP ports
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 3ca4c43a44 |
cmd/mconnect-discover: self announce
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | e50a5745e1 |
discovery: use new UDP port
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 48e691d0b7 |
discovery: self announce
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | a6670d1fcc |
protocol/packet: json field mappings for identity packet
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | ba44186903 |
protocol/packet: encoder
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 19f3aea2dd |
protocol/packet: json field names, settable body
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | a38a88406d |
protocol/packet: renme decoder test
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | f8a3638203 |
protocol/packet: renames
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | cc8e4a80b6 |
discovery: update listener to account for change unmarshaller
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 831273b043 |
protocol/packet: refactor unmarshaller
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | e222ee0509 |
dirs: update path to user data dir
Signed-off-by: Maciek Borzecki <maciek.borzecki@gmail.com> |
7 years ago |
Maciek Borzecki | 26580713f0 | cmd/mconnect-discover: adjust to changes in discovery | 7 years ago |
Maciek Borzecki | a5c696c2da | discovery: rework discovery handling | 7 years ago |
Maciek Borzecki | 64c7ebfac6 | discovery: more verbose logging | 7 years ago |
Maciek Borzecki | e8b7b03e66 | protocol/packet: packet wrappers | 7 years ago |
Maciek Borzecki | 8a41e74076 | dirs: helpers for finding user directories | 7 years ago |
Maciek Borzecki | cd497dae9c | global: add license header | 7 years ago |
Maciek Borzecki | 7d9eac9c0b | cmd/mconnect-discover: simple tool to perform discovery | 7 years ago |
Maciek Borzecki | bb1b9870d8 | discovery: device discovery | 7 years ago |
Maciek Borzecki | 706ac9a0ab | logger: simple logger | 7 years ago |
@ -0,0 +1,119 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package main | |||||
import ( | |||||
"context" | |||||
"os" | |||||
"os/user" | |||||
"github.com/jessevdk/go-flags" | |||||
"github.com/bboozzoo/mconnect/logger" | |||||
"github.com/bboozzoo/mconnect/protocol" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
uflags "github.com/bboozzoo/mconnect/utils/flags" | |||||
) | |||||
var ( | |||||
Stderr = os.Stderr | |||||
Stdout = os.Stdout | |||||
) | |||||
func main() { | |||||
var opts struct { | |||||
Debug bool `short:"d" long:"debug" description:"Show debugging information"` | |||||
Address string `short:"a" long:"address" description:"Address of remote device"` | |||||
} | |||||
_, err := flags.ParseArgs(&opts, os.Args) | |||||
if err != nil { | |||||
uflags.HandleFlagsError(err) | |||||
} | |||||
ctx := context.Background() | |||||
ctx = logger.WithContext(ctx, logger.New()) | |||||
log := logger.FromContext(ctx) | |||||
log.SetLevel(logger.ErrorLevel) | |||||
if opts.Debug { | |||||
log.SetLevel(logger.DebugLevel) | |||||
} | |||||
hostname, err := os.Hostname() | |||||
if err != nil { | |||||
log.Errorf("cannot obtain hostname: %v", err) | |||||
os.Exit(1) | |||||
} | |||||
u, err := user.Current() | |||||
if err != nil { | |||||
log.Errorf("cannot obtain current user: %v", err) | |||||
os.Exit(1) | |||||
} | |||||
entity := u.Name + "@" + hostname | |||||
deviceCert, err := protocol.GenerateDeviceCertificate(entity) | |||||
if err != nil { | |||||
log.Errorf("cannot generate device certificate for entity %q: %v", | |||||
entity, err) | |||||
os.Exit(1) | |||||
} | |||||
conf := protocol.Configuration{ | |||||
Identity: &packet.Identity{ | |||||
DeviceId: "mconnect-" + hostname, | |||||
DeviceName: hostname, | |||||
DeviceType: "computer", | |||||
ProtocolVersion: 7, | |||||
TcpPort: 1716, | |||||
}, | |||||
Cert: deviceCert.TLSCertificate(), | |||||
} | |||||
conn, err := protocol.Dial(ctx, opts.Address, &conf) | |||||
if err != nil { | |||||
log.Errorf("connection failed: %v", err) | |||||
os.Exit(1) | |||||
} | |||||
defer conn.Close() | |||||
for { | |||||
var response *packet.Packet | |||||
p, err := conn.Receive() | |||||
if err != nil { | |||||
log.Errorf("failed to receive packet: %v", err) | |||||
os.Exit(1) | |||||
} | |||||
log.Infof("got packet: %+v", p) | |||||
log.Infof("packet type: %q", p.Type) | |||||
switch p.Type { | |||||
case "kdeconnect.pair": | |||||
log.Infof("pair request") | |||||
pair, err := p.AsPair() | |||||
if err != nil { | |||||
log.Errorf("cannot decode pair packet: %v", err) | |||||
continue | |||||
} | |||||
if pair.Pair { | |||||
response = packet.NewPair() | |||||
} | |||||
} | |||||
if response != nil { | |||||
log.Debugf("sending response: %v", response) | |||||
if err := conn.Send(*response); err != nil { | |||||
log.Errorf("cannot send a response packet: %v", err) | |||||
} | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,103 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package main | |||||
import ( | |||||
"context" | |||||
"fmt" | |||||
"os" | |||||
"time" | |||||
"github.com/jessevdk/go-flags" | |||||
"github.com/bboozzoo/mconnect/discovery" | |||||
"github.com/bboozzoo/mconnect/logger" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
uflags "github.com/bboozzoo/mconnect/utils/flags" | |||||
) | |||||
var ( | |||||
Stderr = os.Stderr | |||||
Stdout = os.Stdout | |||||
) | |||||
func main() { | |||||
var opts struct { | |||||
Debug bool `short:"d" long:"debug" description:"Show debugging information"` | |||||
} | |||||
_, err := flags.ParseArgs(&opts, os.Args) | |||||
if err != nil { | |||||
uflags.HandleFlagsError(err) | |||||
} | |||||
ctx := context.Background() | |||||
ctx = logger.WithContext(ctx, logger.New()) | |||||
log := logger.FromContext(ctx) | |||||
log.SetLevel(logger.ErrorLevel) | |||||
if opts.Debug { | |||||
log.SetLevel(logger.DebugLevel) | |||||
} | |||||
log.Infof("setting up listener") | |||||
l, err := discovery.NewListener() | |||||
if err != nil { | |||||
fmt.Fprintf(Stderr, "error: failed to setup listener: %v\n", | |||||
err) | |||||
os.Exit(1) | |||||
} | |||||
hostname, err := os.Hostname() | |||||
if err != nil { | |||||
fmt.Fprintf(Stderr, "error: failed to obtain hostname: %v\n", | |||||
err) | |||||
os.Exit(1) | |||||
} | |||||
go func() { | |||||
for { | |||||
err := discovery.Announce(ctx, packet.Identity{ | |||||
DeviceId: "mconnect-" + hostname, | |||||
DeviceName: hostname, | |||||
DeviceType: "computer", | |||||
ProtocolVersion: 7, | |||||
TcpPort: 1716, | |||||
}) | |||||
if err != nil { | |||||
log.Errorf("failed to self announce: %v", err) | |||||
} | |||||
time.Sleep(5 * time.Second) | |||||
} | |||||
}() | |||||
devices := map[string]*discovery.Discovery{} | |||||
for { | |||||
log.Info("receive wait") | |||||
d, err := l.Receive(ctx) | |||||
if err != nil { | |||||
log.Warning("failed to receive identity packet: %v", err) | |||||
continue | |||||
} | |||||
log.Infof("discovered a device at %s packet: %v", | |||||
d.From, d.Identity) | |||||
if _, ok := devices[d.Identity.DeviceId]; !ok { | |||||
devices[d.Identity.DeviceId] = d | |||||
fmt.Fprintf(Stdout, " * %q (ID: %v) %v:%v\n", | |||||
d.Identity.DeviceName, | |||||
d.Identity.DeviceId, | |||||
d.From.IP, d.Identity.TcpPort) | |||||
} | |||||
} | |||||
} |
@ -0,0 +1,41 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package dirs | |||||
import ( | |||||
"os/user" | |||||
"path" | |||||
) | |||||
var userHome string | |||||
func UserHome() string { | |||||
if userHome == "" { | |||||
user, _ := user.Current() | |||||
if user != nil { | |||||
userHome = user.HomeDir | |||||
} | |||||
} | |||||
return userHome | |||||
} | |||||
func UserCache() string { | |||||
return path.Join(UserHome(), ".cache", "mconnect") | |||||
} | |||||
func UserConfig() string { | |||||
return path.Join(UserHome(), ".config", "mconnect") | |||||
} | |||||
func UserData() string { | |||||
return path.Join(UserHome(), ".local", "share", "mconnect") | |||||
} |
@ -0,0 +1,29 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package dirs_test | |||||
import ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/bboozzoo/mconnect/dirs" | |||||
) | |||||
func TestUserDirs(t *testing.T) { | |||||
restore := dirs.MockUserHome("/home/foo") | |||||
defer restore() | |||||
assert.Equal(t, dirs.UserCache(), "/home/foo/.cache/mconnect") | |||||
assert.Equal(t, dirs.UserConfig(), "/home/foo/.config/mconnect") | |||||
assert.Equal(t, dirs.UserData(), "/home/foo/.local/share/mconnect") | |||||
} |
@ -0,0 +1,20 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package dirs | |||||
func MockUserHome(home string) (restore func()) { | |||||
old := userHome | |||||
userHome = home | |||||
return func() { | |||||
userHome = old | |||||
} | |||||
} |
@ -0,0 +1,42 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package discovery | |||||
import ( | |||||
"context" | |||||
"net" | |||||
"github.com/pkg/errors" | |||||
"github.com/bboozzoo/mconnect/protocol" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
) | |||||
func Announce(ctx context.Context, identity packet.Identity) error { | |||||
p := packet.New("kdeconnect.identity", identity) | |||||
data, err := packet.Marshal(p) | |||||
if err != nil { | |||||
return errors.Wrapf(err, "failed to marshal packet") | |||||
} | |||||
c, err := net.DialUDP("udp", nil, protocol.UDPDiscoveryAddr) | |||||
if err != nil { | |||||
return errors.Wrapf(err, "failed to open UDP socket") | |||||
} | |||||
defer c.Close() | |||||
_, err = c.Write(data) | |||||
if err != nil { | |||||
return errors.Wrapf(err, "failed to send identity packet") | |||||
} | |||||
return nil | |||||
} |
@ -0,0 +1,78 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package discovery | |||||
import ( | |||||
"context" | |||||
"net" | |||||
"github.com/pkg/errors" | |||||
"github.com/bboozzoo/mconnect/logger" | |||||
"github.com/bboozzoo/mconnect/protocol" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
) | |||||
type Listener struct { | |||||
conn *net.UDPConn | |||||
} | |||||
func NewListener() (*Listener, error) { | |||||
conn, err := net.ListenUDP("udp", protocol.UDPDiscoveryAddr) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
listener := &Listener{ | |||||
conn: conn, | |||||
} | |||||
return listener, nil | |||||
} | |||||
// Discovery conveys the received discovery information | |||||
type Discovery struct { | |||||
// Packet is the original packet received | |||||
Packet *packet.Packet | |||||
// Identity is the parsed identity data | |||||
Identity *packet.Identity | |||||
// From is the address the packet was received from | |||||
From *net.UDPAddr | |||||
} | |||||
// Receive blocks waiting to receive a discovery packet. Once received, it will | |||||
// parse the packet and return a result. | |||||
func (l *Listener) Receive(ctx context.Context) (*Discovery, error) { | |||||
log := logger.FromContext(ctx) | |||||
buf := make([]byte, 4096) | |||||
count, addr, err := l.conn.ReadFromUDP(buf) | |||||
if err != nil { | |||||
return nil, errors.Wrap(err, "failed to receive a packet") | |||||
} | |||||
log.Debugf("got %v bytes from %v", count, addr) | |||||
log.Debugf("data:\n%s", string(buf)) | |||||
var p packet.Packet | |||||
if err := packet.Unmarshal(buf, &p); err != nil { | |||||
return nil, errors.Wrap(err, "failed to parse packet") | |||||
} | |||||
identity, err := p.AsIdentity() | |||||
if err != nil { | |||||
return nil, errors.Wrap(err, "failed to parse as identity packet") | |||||
} | |||||
discovery := &Discovery{ | |||||
Packet: &p, | |||||
Identity: identity, | |||||
From: addr, | |||||
} | |||||
return discovery, nil | |||||
} |
@ -0,0 +1,17 @@ | |||||
module github.com/bboozzoo/mconnect | |||||
go 1.13 | |||||
require ( | |||||
github.com/Sirupsen/logrus v1.0.4 | |||||
github.com/godbus/dbus v4.1.0+incompatible | |||||
github.com/jessevdk/go-flags v1.3.0 | |||||
github.com/onsi/ginkgo v1.11.0 // indirect | |||||
github.com/onsi/gomega v1.8.1 // indirect | |||||
github.com/pkg/errors v0.8.0 | |||||
github.com/sirupsen/logrus v1.4.2 // indirect | |||||
github.com/stretchr/testify v1.2.2 | |||||
golang.org/x/crypto v0.0.0-20180119165957-a66000089151 // indirect | |||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect | |||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect | |||||
) |
@ -0,0 +1,33 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package logger | |||||
import ( | |||||
"context" | |||||
) | |||||
type loggerKeyType int | |||||
const ( | |||||
loggerContextKey loggerKeyType = 1 | |||||
) | |||||
func FromContext(ctx context.Context) Logger { | |||||
if logger, _ := ctx.Value(loggerContextKey).(Logger); logger != nil { | |||||
return logger | |||||
} | |||||
return New() | |||||
} | |||||
func WithContext(ctx context.Context, logger Logger) context.Context { | |||||
return context.WithValue(ctx, loggerContextKey, logger) | |||||
} |
@ -0,0 +1,51 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package logger | |||||
type Logger interface { | |||||
Debug(args ...interface{}) | |||||
Debugf(format string, args ...interface{}) | |||||
Debugln(args ...interface{}) | |||||
Info(args ...interface{}) | |||||
Infof(format string, args ...interface{}) | |||||
Infoln(args ...interface{}) | |||||
Error(args ...interface{}) | |||||
Errorf(format string, args ...interface{}) | |||||
Errorln(args ...interface{}) | |||||
Warning(args ...interface{}) | |||||
Warningf(format string, args ...interface{}) | |||||
Warningln(args ...interface{}) | |||||
Panic(args ...interface{}) | |||||
Panicf(format string, args ...interface{}) | |||||
Panicln(args ...interface{}) | |||||
Print(args ...interface{}) | |||||
Printf(format string, args ...interface{}) | |||||
Println(args ...interface{}) | |||||
SetLevel(level Level) | |||||
} | |||||
type Level int | |||||
const ( | |||||
PanicLevel Level = iota | |||||
FatalLevel | |||||
ErrorLevel | |||||
WarnLevel | |||||
InfoLevel | |||||
DebugLevel | |||||
) |
@ -0,0 +1,45 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package logger | |||||
import ( | |||||
"os" | |||||
"github.com/Sirupsen/logrus" | |||||
) | |||||
type logger struct { | |||||
logrus.Logger | |||||
} | |||||
var log *logger | |||||
func init() { | |||||
log = &logger{ | |||||
Logger: logrus.Logger{ | |||||
Out: os.Stderr, | |||||
Formatter: &logrus.TextFormatter{ | |||||
FullTimestamp: true, | |||||
}, | |||||
Hooks: make(logrus.LevelHooks), | |||||
Level: logrus.DebugLevel, | |||||
}, | |||||
} | |||||
} | |||||
func New() Logger { | |||||
return log | |||||
} | |||||
func (l *logger) SetLevel(level Level) { | |||||
l.Logger.SetLevel(logrus.Level(level)) | |||||
} |
@ -0,0 +1,77 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package protocol | |||||
import ( | |||||
"crypto/ecdsa" | |||||
"crypto/elliptic" | |||||
"crypto/rand" | |||||
"crypto/tls" | |||||
"crypto/x509" | |||||
"crypto/x509/pkix" | |||||
"math/big" | |||||
"time" | |||||
) | |||||
type DeviceCertificate struct { | |||||
key *ecdsa.PrivateKey | |||||
cert []byte | |||||
} | |||||
func (d *DeviceCertificate) TLSCertificate() *tls.Certificate { | |||||
return &tls.Certificate{ | |||||
PrivateKey: d.key, | |||||
Certificate: [][]byte{d.cert}, | |||||
} | |||||
} | |||||
// GenerateDeviceCertificate returns a device certificate | |||||
func GenerateDeviceCertificate(entity string) (*DeviceCertificate, error) { | |||||
limit := big.Int{} | |||||
limit.Lsh(big.NewInt(1), 128) | |||||
serial, err := rand.Int(rand.Reader, &limit) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
priv, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
startTime := time.Now() | |||||
// 10 years from now | |||||
expireTime := startTime.AddDate(10, 0, 0) | |||||
template := x509.Certificate{ | |||||
SerialNumber: serial, | |||||
Subject: pkix.Name{ | |||||
CommonName: entity, | |||||
Organization: []string{"mconnect"}, | |||||
OrganizationalUnit: []string{"mconnect"}, | |||||
}, | |||||
NotBefore: startTime, | |||||
NotAfter: expireTime, | |||||
BasicConstraintsValid: true, | |||||
} | |||||
selfSign := template | |||||
cert, err := x509.CreateCertificate(rand.Reader, &template, &selfSign, | |||||
&priv.PublicKey, priv) | |||||
if err != nil { | |||||
return nil, err | |||||
} | |||||
devcert := &DeviceCertificate{ | |||||
key: priv, | |||||
cert: cert, | |||||
} | |||||
return devcert, nil | |||||
} |
@ -0,0 +1,85 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package protocol | |||||
import ( | |||||
"context" | |||||
"crypto/tls" | |||||
"net" | |||||
"github.com/pkg/errors" | |||||
"github.com/bboozzoo/mconnect/logger" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
) | |||||
type Connection struct { | |||||
conn *tls.Conn | |||||
} | |||||
type Configuration struct { | |||||
Cert *tls.Certificate | |||||
Identity *packet.Identity | |||||
} | |||||
func Dial(ctx context.Context, where string, conf *Configuration) (*Connection, error) { | |||||
log := logger.FromContext(ctx) | |||||
dialer := net.Dialer{} | |||||
conn, err := dialer.DialContext(ctx, "tcp", where) | |||||
if err != nil { | |||||
return nil, errors.Wrapf(err, "failed to dial %s", where) | |||||
} | |||||
log.Debugf("connected to %v", conn.RemoteAddr()) | |||||
e := packet.NewEncoder(conn) | |||||
if err := e.Encode(packet.NewIdentity(conf.Identity)); err != nil { | |||||
return nil, errors.Wrapf(err, "failed to send identity") | |||||
} | |||||
log.Debugf("identity sent") | |||||
// upgrade to secure connection | |||||
tlsConf := tls.Config{ | |||||
InsecureSkipVerify: true, | |||||
Certificates: []tls.Certificate{*conf.Cert}, | |||||
} | |||||
tlsConn := tls.Server(conn, &tlsConf) | |||||
if err := tlsConn.Handshake(); err != nil { | |||||
log.Errorf("TLS handshake failed: %v", err) | |||||
return nil, err | |||||
} | |||||
return &Connection{conn: tlsConn}, nil | |||||
} | |||||
func (c *Connection) Close() error { | |||||
if c.conn != nil { | |||||
c.conn.Close() | |||||
} | |||||
return nil | |||||
} | |||||
func (c *Connection) Receive() (*packet.Packet, error) { | |||||
d := packet.NewDecoder(c.conn) | |||||
var p packet.Packet | |||||
if err := d.Decode(&p); err != nil { | |||||
return nil, err | |||||
} | |||||
return &p, nil | |||||
} | |||||
func (c *Connection) Send(p packet.Packet) error { | |||||
e := packet.NewEncoder(c.conn) | |||||
return e.Encode(&p) | |||||
} |
@ -0,0 +1,20 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package protocol | |||||
import ( | |||||
"net" | |||||
) | |||||
func Listener(addr string) (net.Listener, error) { | |||||
return net.Listen("tcp", addr) | |||||
} |
@ -0,0 +1,52 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
"fmt" | |||||
"io" | |||||
"github.com/pkg/errors" | |||||
) | |||||
func Unmarshal(data []byte, p *Packet) error { | |||||
return NewDecoder(bytes.NewBuffer(data)).Decode(p) | |||||
} | |||||
type Decoder struct { | |||||
r io.Reader | |||||
j *json.Decoder | |||||
} | |||||
func NewDecoder(r io.Reader) *Decoder { | |||||
return &Decoder{ | |||||
r: r, | |||||
j: json.NewDecoder(r), | |||||
} | |||||
} | |||||
func (d *Decoder) Decode(p *Packet) error { | |||||
if p == nil { | |||||
return fmt.Errorf("no packet") | |||||
} | |||||
if err := d.j.Decode(p); err != nil { | |||||
return errors.Wrap(err, "failed to decode body") | |||||
} | |||||
if p.Id == uint64(0) || p.Type == "" || p.Body == nil { | |||||
return fmt.Errorf("packet incomplete, missing id, type or body") | |||||
} | |||||
return nil | |||||
} |
@ -0,0 +1,71 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet_test | |||||
import ( | |||||
"strings" | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
) | |||||
func TestUnmarshal(t *testing.T) { | |||||
var p packet.Packet | |||||
err := packet.Unmarshal([]byte(`foobar`), &p) | |||||
assert.Error(t, err) | |||||
p = packet.Packet{} | |||||
err = packet.Unmarshal([]byte(`{}`), &p) | |||||
assert.Error(t, err) | |||||
p = packet.Packet{} | |||||
err = packet.Unmarshal([]byte(`{"id": 123, "type": "foo","body":{}}`), &p) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, p, packet.Packet{ | |||||
Id: uint64(123), | |||||
Type: "foo", | |||||
Body: []byte("{}"), | |||||
}) | |||||
} | |||||
func TestDecoder(t *testing.T) { | |||||
input := ` | |||||
{"id": 123, "type": "foo","body":{}} | |||||
{"id": 456, "type": "bar","body":{"123": 123}} | |||||
{"id": 678, "type": "baz" | |||||
` | |||||
d := packet.NewDecoder(strings.NewReader(input)) | |||||
p := packet.Packet{} | |||||
err := d.Decode(&p) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, p, packet.Packet{ | |||||
Id: uint64(123), | |||||
Type: "foo", | |||||
Body: []byte("{}"), | |||||
}) | |||||
p = packet.Packet{} | |||||
err = d.Decode(&p) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, p, packet.Packet{ | |||||
Id: uint64(456), | |||||
Type: "bar", | |||||
Body: []byte(`{"123": 123}`), | |||||
}) | |||||
p = packet.Packet{} | |||||
err = d.Decode(&p) | |||||
assert.Error(t, err) | |||||
} |
@ -0,0 +1,16 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
func NewDiscovery() { | |||||
} |
@ -0,0 +1,81 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
import ( | |||||
"bytes" | |||||
"encoding/json" | |||||
"io" | |||||
"time" | |||||
"github.com/pkg/errors" | |||||
) | |||||
var getId = func() uint64 { | |||||
return uint64(time.Now().UnixNano() / 1000) | |||||
} | |||||
func Marshal(p *Packet) ([]byte, error) { | |||||
b := &bytes.Buffer{} | |||||
enc := NewEncoder(b) | |||||
if err := enc.Encode(p); err != nil { | |||||
return nil, err | |||||
} | |||||
return b.Bytes(), nil | |||||
} | |||||
type Encoder struct { | |||||
w io.Writer | |||||
j *json.Encoder | |||||
} | |||||
func NewEncoder(w io.Writer) *Encoder { | |||||
return &Encoder{ | |||||
w: w, | |||||
j: json.NewEncoder(w), | |||||
} | |||||
} | |||||
type auxPacket struct { | |||||
Packet | |||||
Body interface{} `json:"body"` | |||||
} | |||||
func (e *Encoder) Encode(p *Packet) error { | |||||
if p == nil { | |||||
return errors.New("no packet") | |||||
} | |||||
if p.Type == "" { | |||||
return errors.New("packet type not set") | |||||
} | |||||
id := p.Id | |||||
if id == 0 { | |||||
id = getId() | |||||
} | |||||
body := p.auxBody | |||||
// encodes packet and appends a newline character | |||||
err := e.j.Encode(auxPacket{ | |||||
Packet: Packet{ | |||||
Id: id, | |||||
Type: p.Type, | |||||
}, | |||||
Body: body, | |||||
}) | |||||
if err != nil { | |||||
return errors.Wrap(err, "failed to encode body") | |||||
} | |||||
return nil | |||||
} |
@ -0,0 +1,52 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet_test | |||||
import ( | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
) | |||||
func TestMarshal(t *testing.T) { | |||||
data, err := packet.Marshal(nil) | |||||
assert.Error(t, err) | |||||
data, err = packet.Marshal(&packet.Packet{}) | |||||
assert.Error(t, err) | |||||
exp := `{"id":123,"type":"foo","body":null}` + "\n" | |||||
p := packet.New("foo", nil) | |||||
p.Id = 123 | |||||
data, err = packet.Marshal(p) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, []byte(exp), data) | |||||
exp = `{"id":123,"type":"foo","body":{}}` + "\n" | |||||
p = packet.New("foo", map[int]int{}) | |||||
p.Id = 123 | |||||
data, err = packet.Marshal(p) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, []byte(exp), data) | |||||
restore := packet.MockGetId(func() uint64 { | |||||
return uint64(889911) | |||||
}) | |||||
defer restore() | |||||
exp = `{"id":889911,"type":"foo","body":{}}` + "\n" | |||||
data, err = packet.Marshal(packet.New("foo", map[int]int{})) | |||||
assert.NoError(t, err) | |||||
assert.Equal(t, []byte(exp), data) | |||||
} |
@ -0,0 +1,20 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
func MockGetId(newGetId func() uint64) (restore func()) { | |||||
old := getId | |||||
getId = newGetId | |||||
return func() { | |||||
getId = old | |||||
} | |||||
} |
@ -0,0 +1,42 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
import ( | |||||
"encoding/json" | |||||
"fmt" | |||||
) | |||||
type Identity struct { | |||||
DeviceId string `json:"deviceId"` | |||||
DeviceName string `json:"deviceName"` | |||||
DeviceType string `json:"deviceType"` | |||||
ProtocolVersion uint `json:"protocolVersion"` | |||||
IncomingCapabilities []string `json:"incomingCapabilities"` | |||||
OutgoingCapabilities []string `json:"outgoingCapabilities"` | |||||
TcpPort uint `json:"tcpPort"` | |||||
} | |||||
func (p *Packet) AsIdentity() (*Identity, error) { | |||||
if p.Type != "kdeconnect.identity" { | |||||
return nil, fmt.Errorf("not an identity packet, unexpected type %q", p.Type) | |||||
} | |||||
var identity Identity | |||||
if err := json.Unmarshal(p.Body, &identity); err != nil { | |||||
return nil, err | |||||
} | |||||
return &identity, nil | |||||
} | |||||
func NewIdentity(identity *Identity) *Packet { | |||||
return New("kdeconnect.identity", identity) | |||||
} |
@ -0,0 +1,39 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet_test | |||||
import ( | |||||
"encoding/json" | |||||
"testing" | |||||
"github.com/stretchr/testify/assert" | |||||
"github.com/bboozzoo/mconnect/protocol/packet" | |||||
) | |||||
func TestPacketAsIdentity(t *testing.T) { | |||||
p := packet.Packet{ | |||||
Type: "kdeconnect.ping", | |||||
Body: json.RawMessage(`foo`), | |||||
} | |||||
i, err := p.AsIdentity() | |||||
assert.Nil(t, i) | |||||
assert.Error(t, err) | |||||
p = packet.Packet{ | |||||
Type: "kdeconnect.identity", | |||||
Body: json.RawMessage(`{}`), | |||||
} | |||||
i, err = p.AsIdentity() | |||||
assert.NotNil(t, i) | |||||
assert.NoError(t, err) | |||||
} |
@ -0,0 +1,30 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
import ( | |||||
"encoding/json" | |||||
) | |||||
type Packet struct { | |||||
Id uint64 `json:"id"` | |||||
Type string `json:"type"` | |||||
Body json.RawMessage `json:"body"` | |||||
auxBody interface{} | |||||
} | |||||
func New(typ string, body interface{}) *Packet { | |||||
return &Packet{ | |||||
Type: typ, | |||||
auxBody: body, | |||||
} | |||||
} |
@ -0,0 +1,36 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package packet | |||||
import ( | |||||
"encoding/json" | |||||
"fmt" | |||||
) | |||||
type Pair struct { | |||||
Pair bool `json:"pair"` | |||||
} | |||||
func NewPair() *Packet { | |||||
return New("kdeconnect.pair", Pair{Pair: true}) | |||||
} | |||||
func (p *Packet) AsPair() (*Pair, error) { | |||||
if p.Type != "kdeconnect.pair" { | |||||
return nil, fmt.Errorf("not a pair packet, unexpected type %q", p.Type) | |||||
} | |||||
var pair Pair | |||||
if err := json.Unmarshal(p.Body, &pair); err != nil { | |||||
return nil, err | |||||
} | |||||
return &pair, nil | |||||
} |
@ -0,0 +1,35 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package protocol | |||||
import ( | |||||
"net" | |||||
) | |||||
const ( | |||||
// UDPPort is the UDP port used for discovery | |||||
UDPPort = 1716 | |||||
// UDPPortOld is the UDP port used by older versions of the protocol | |||||
UDPPortOld = 1714 | |||||
// TCPPortMin is the minimum TCP port number | |||||
TCPPortMin = 1716 | |||||
PayloadTransferPortMin = 1739 | |||||
) | |||||
var ( | |||||
// UDPDiscoveryAddr is the UDP address used for discovery | |||||
UDPDiscoveryAddr = &net.UDPAddr{ | |||||
Port: UDPPort, | |||||
IP: net.ParseIP("255.255.255.255"), | |||||
} | |||||
) |
@ -0,0 +1,36 @@ | |||||
// Licensed under the Apache License, Version 2.0 (the "License"); | |||||
// you may not use this file except in compliance with the License. | |||||
// You may obtain a copy of the License at | |||||
// | |||||
// http://www.apache.org/licenses/LICENSE-2.0 | |||||
// | |||||
// Unless required by applicable law or agreed to in writing, software | |||||
// distributed under the License is distributed on an "AS IS" BASIS, | |||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
// See the License for the specific language governing permissions and | |||||
// limitations under the License. | |||||
package flags | |||||
import ( | |||||
"fmt" | |||||
"os" | |||||
"github.com/jessevdk/go-flags" | |||||
) | |||||
func IsErrHelp(err error) bool { | |||||
ferr, ok := err.(*flags.Error) | |||||
return ok && ferr.Type == flags.ErrHelp | |||||
} | |||||
func HandleFlagsError(err error) { | |||||
if err == nil { | |||||
panic(fmt.Sprintf("expected an error, got %v", err)) | |||||
} | |||||
if IsErrHelp(err) { | |||||
os.Exit(0) | |||||
} | |||||
os.Exit(1) | |||||
} |