Commit 31fed94d authored by Sophie Brun's avatar Sophie Brun

New upstream version 2.18

parent 98e17dd0
......@@ -11,3 +11,4 @@ prime/
snap/
stage/
/snap
......@@ -4,7 +4,7 @@ FROM golang:alpine AS build-env
ENV SRC_DIR $GOPATH/src/github.com/bettercap/bettercap
RUN apk add --update ca-certificates
RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev linux-headers libnetfilter_queue-dev git
RUN apk add --no-cache --update bash iptables wireless-tools build-base libpcap-dev libusb-1.0-dev linux-headers libnetfilter_queue-dev git
WORKDIR $SRC_DIR
ADD . $SRC_DIR
......
......@@ -9,6 +9,22 @@
revision = "a32116e4989e2b0e17c057ee378b4d5246add74e"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:1d692605b66a4fbe2a3de27131619e8e2c88ccf142ee43a7a8c902339942e0cc"
name = "github.com/antchfx/jsonquery"
packages = ["."]
pruneopts = "UT"
revision = "a2896be8c82bb2229d1cf26204863180e34b2b31"
[[projects]]
branch = "master"
digest = "1:7e4a9eaa42cb3b4cd48bd0cd2072a029fe25a47af20cfac529ea8c365f8559ac"
name = "github.com/antchfx/xpath"
packages = ["."]
pruneopts = "UT"
revision = "c8489ed3251e7d55ec2b7f18a2bc3a9a7222f0af"
[[projects]]
branch = "master"
digest = "1:c309b41787813f80ec393023471014b0be16346bba6e16bf5fa01ce1d310a4ea"
......@@ -26,6 +42,14 @@
pruneopts = "UT"
revision = "1353e80bee488dc02d1f7e42759c1352492bf18b"
[[projects]]
branch = "master"
digest = "1:c93fdd8820c13c2e2000d3064c510dde1397edca5ca1533fd15943402dab92b0"
name = "github.com/bettercap/nrf24"
packages = ["."]
pruneopts = "UT"
revision = "aa37e6d0e0eb125cee9ec71ed694db2ad58b509a"
[[projects]]
digest = "1:b95738a1e6ace058b5b8544303c0871fc01d224ef0d672f778f696265d0f2917"
name = "github.com/bettercap/readline"
......@@ -59,7 +83,7 @@
revision = "2ce16c963a8ac5bd6af851d4877e38701346983f"
[[projects]]
digest = "1:c706034658cd03896bcde87e0770048bb1443378d18932d5897c3d7f3e44133b"
digest = "1:cbae049ade5f135f52da4cc6e3fd6aca532dffbde65160ad1510dee6c3e8dc4f"
name = "github.com/evilsocket/islazy"
packages = [
"data",
......@@ -72,8 +96,8 @@
"zip",
]
pruneopts = "UT"
revision = "268495ba2f4621f397a274dc33b9296553db855d"
version = "v1.10.0"
revision = "949884336bbf7e535e4e0e6a3f75af89091488e5"
version = "v1.10.1"
[[projects]]
branch = "master"
......@@ -120,6 +144,14 @@
pruneopts = "UT"
revision = "v1.1.16"
[[projects]]
branch = "master"
digest = "1:2ef895ea08a0af10ad6f1e1faf631c82fa5413dcf0ada93eb62ab5ad02df4979"
name = "github.com/google/gousb"
packages = ["."]
pruneopts = "UT"
revision = "d0c05ab7f70d6f6dc60ecd184517cb5e25860657"
[[projects]]
digest = "1:ca59b1175189b3f0e9f1793d2c350114be36eaabbe5b9f554b35edee1de50aea"
name = "github.com/gorilla/mux"
......@@ -190,11 +222,11 @@
[[projects]]
branch = "master"
digest = "1:450f3a64f7a76d3f229ff33a30f0a889629dde46b0f82ece7c53404528316665"
digest = "1:34fe44dd2bbe5723068e0a7a266847965a88297d383fe611e0358e556d82de09"
name = "github.com/mdlayher/raw"
packages = ["."]
pruneopts = "UT"
revision = "fa5ef3332ca961deab5782da07b1616fea8a9dd8"
revision = "480b93709cce56651807d3fdeb260a5a7c4e2d5f"
[[projects]]
branch = "master"
......@@ -254,11 +286,11 @@
[[projects]]
branch = "master"
digest = "1:8207c052fb873f83c61a5aa16f6add5feb9881eda2112b56f69fd3b9e7f55c3f"
digest = "1:7a8067e267a25694dd7bdde6e5016053a3f61bb7141448bf8e0025a84eb7d11c"
name = "golang.org/x/sys"
packages = ["unix"]
pruneopts = "UT"
revision = "983097b1a8a340cd1cc7df17d735154d89e10b1a"
revision = "a9d3bda3a223baa6bba6ef412cb273f0fd163c05"
[[projects]]
digest = "1:9935525a8c49b8434a0b0a54e1980e94a6fae73aaff45c5d33ba8dff69de123e"
......@@ -276,7 +308,9 @@
analyzer-version = 1
input-imports = [
"github.com/adrianmo/go-nmea",
"github.com/antchfx/jsonquery",
"github.com/bettercap/gatt",
"github.com/bettercap/nrf24",
"github.com/bettercap/readline",
"github.com/chifflier/nfqueue-go/nfqueue",
"github.com/dustin/go-humanize",
......
......@@ -9,44 +9,36 @@
</p>
</p>
**bettercap** is the Swiss Army knife for 802.11, BLE and Ethernet networks reconnaissance and attacks.
bettercap is a powerful, easily extensible and portable framework written in Go which aims to offer to security researchers, red teamers and reverse engineers an **easy to use**, **all-in-one solution** with all the features they might possibly need for performing reconnaissance and attacking [WiFi](https://www.bettercap.org/modules/wifi/) networks, [Bluetooth Low Energy](https://www.bettercap.org/modules/ble/) devices, wireless [HID](https://www.bettercap.org/modules/hid/) devices and [Ethernet](https://www.bettercap.org/modules/ethernet) networks.
## How to Install
## Main Features
<p align="center">
<a href="https://snapcraft.io/bettercap" target="_blank">
<img alt="Get it from the Snap Store" src="https://snapcraft.io/static/images/badges/en/snap-store-white.svg" />
</a>
</p>
A [precompiled version is available](https://github.com/bettercap/bettercap/releases) for each release, alternatively you can use the latest version of the source code from this repository in order to build your own binary.
Make sure you have a correctly configured **Go >= 1.8** environment, that `$GOPATH/bin` is in `$PATH`, that the `libpcap-dev` and `libnetfilter-queue-dev` (this one is only required on Linux) package installed for your system and then:
$ go get github.com/bettercap/bettercap
$ cd $GOPATH/src/github.com/bettercap/bettercap
$ make build && sudo make install
This command will download bettercap, install its dependencies, compile it and move the `bettercap` executable to `/usr/local/bin`.
Now you can use `sudo bettercap -h` to show the basic command line options and just `sudo bettercap` to start an
[interactive session](https://github.com/bettercap/bettercap/wiki/Interactive-Mode) on your default network interface, otherwise you can [load a caplet](https://github.com/bettercap/bettercap/wiki/Caplets).
* **WiFi** networks scanning, [deauthentication attack](https://www.evilsocket.net/2018/07/28/Project-PITA-Writeup-build-a-mini-mass-deauther-using-bettercap-and-a-Raspberry-Pi-Zero-W/), [clientless PMKID association attack](https://www.evilsocket.net/2019/02/13/Pwning-WiFi-networks-with-bettercap-and-the-PMKID-client-less-attack/) and automatic WPA/WPA2 client handshakes capture.
* **Bluetooth Low Energy** devices scanning, characteristics enumeration, reading and writing.
* 2.4Ghz wireless devices scanning and **MouseJacking** attacks with over-the-air HID frames injection (with DuckyScript support).
* Passive and active IP network hosts probing and recon.
* **ARP, DNS and DHCPv6 spoofers** for MITM attacks on IP based networks.
* **Proxies at packet level, TCP level and HTTP/HTTPS** application level fully scriptable with easy to implement **javascript plugins**.
* A powerful **network sniffer** for **credentials harvesting** which can also be used as a **network protocol fuzzer**.
* A very fast port scanner.
* A powerful [REST API](https://www.bettercap.org/modules/core/api.rest/) with support for asynchronous events notification on websocket to orchestrate your attacks easily.
* [More!](https://www.bettercap.org/modules/)
Once bettercap is installed, you can download/update system caplet with the command:
## About the 1.x Legacy Version
sudo bettercap -eval "caplets.update; q"
While the first version (up to 1.6.2) of bettercap was implemented in Ruby and only offered basic MITM, sniffing and proxying capabilities, the 2.x is a complete reimplementation using the [Go programming language](https://golang.org/).
## Update
This ground-up rewrite offered several advantages:
In order to update to an unstable but bleeding edge release from this repository, run the commands below:
* bettercap can now be distributed as a **single binary** with very few dependencies, for basically **any OS and any architecture**.
* 1.x proxies, altough highly optimized and event based, **[used to bottleneck the entire network](https://en.wikipedia.org/wiki/Global_interpreter_lock)** when performing a MITM attack, while the new version adds almost no overhead.
* Due to such **performance and functional limitations**, most of the features that the 2.x version is offering were simply impossible to implement properly (read as: without killing the entire network ... or your computer).
$ go get -u github.com/bettercap/bettercap
$ cd $GOPATH/src/github.com/bettercap/bettercap
$ make build && sudo make install
For this reason, **any version prior to 2.x is considered deprecated** and any type of support has been dropped in favor of the new implementation. An archived copy of the legacy documentation is [available here](https://www.bettercap.org/legacy/), however **it is strongly suggested to upgrade**.
## Documentation and Examples
The project is documented [in this wiki](https://github.com/bettercap/bettercap/wiki).
The project is documented [here](https://www.bettercap.org/).
## License
......
#!/bin/bash
BUILD_FOLDER=build
VERSION=$(cat core/banner.go | grep Version | cut -d '"' -f 2)
CROSS_LIB=-L/tmp/libpcap-1.8.1/
CROSS_LIB="-L/tmp/libpcap-1.8.1/ -L/tmp/libusb-1.0.22/"
bin_dep() {
BIN=$1
......@@ -46,6 +46,19 @@ download_pcap() {
tar xf libpcap-1.8.1.tar.gz
}
download_libusb() {
bin_dep 'wget'
bin_dep 'tar'
cd /tmp
rm -rf libusb-1.0.22.tar.bz2
if [ ! -f /tmp/libusb-1.0.22.tar.bz2 ]; then
echo "@ Downloading https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.tar.bz2 ..."
wget -q https://github.com/libusb/libusb/releases/download/v1.0.22/libusb-1.0.22.tar.bz2 -O /tmp/libusb-1.0.22.tar.bz2
fi
tar xf libusb-1.0.22.tar.bz2
}
xcompile_pcap() {
ARCH=$1
HOST=$2
......@@ -63,6 +76,21 @@ xcompile_pcap() {
make CFLAGS='-w' -j4 > /dev/null
}
xcompile_libusb() {
ARCH=$1
HOST=$2
COMPILER=$3
bin_dep 'make'
bin_dep "$COMPILER"
echo "@ Cross compiling libusb for $ARCH with $COMPILER ..."
cd /tmp/libusb-1.0.22
export CC=$COMPILER
./configure --host=$HOST > /dev/null
make CFLAGS='-w' -j4 > /dev/null
}
build_linux_amd64() {
echo "@ Building linux/amd64 ..."
go build -o bettercap ..
......@@ -73,6 +101,8 @@ build_linux_arm7_static() {
download_pcap
xcompile_pcap 'arm' 'arm-linux-gnueabi' 'arm-linux-gnueabi-gcc'
download_libusb
xcompile_libusb 'arm' 'arm-linux-gnueabi' 'arm-linux-gnueabi-gcc'
echo "@ Building linux/arm7 ..."
cd "$OLD"
......@@ -84,6 +114,8 @@ build_linux_arm7hf_static() {
download_pcap
xcompile_pcap 'arm' 'arm-linux-gnueabihf' 'arm-linux-gnueabihf-gcc'
download_libusb
xcompile_libusb 'arm' 'arm-linux-gnueabihf' 'arm-linux-gnueabihf-gcc'
echo "@ Building linux/arm7hf ..."
cd "$OLD"
......@@ -95,6 +127,8 @@ build_linux_mips_static() {
download_pcap
xcompile_pcap 'mips' 'mips-linux-gnu' 'mips-linux-gnu-gcc'
download_libusb
xcompile_libusb 'mips' 'mips-linux-gnu' 'mips-linux-gnu-gcc'
echo "@ Building linux/mips ..."
cd "$OLD"
......@@ -106,6 +140,8 @@ build_linux_mipsle_static() {
download_pcap
xcompile_pcap 'mipsel' 'mipsel-linux-gnu' 'mipsel-linux-gnu-gcc'
download_libusb
xcompile_libusb 'mipsel' 'mipsel-linux-gnu' 'mipsel-linux-gnu-gcc'
echo "@ Building linux/mipsle ..."
cd "$OLD"
......@@ -117,6 +153,8 @@ build_linux_mips64_static() {
download_pcap
xcompile_pcap 'mips64' 'mips64-linux-gnuabi64' 'mips64-linux-gnuabi64-gcc'
download_libusb
xcompile_libusb 'mips64' 'mips64-linux-gnuabi64' 'mips64-linux-gnuabi64-gcc'
echo "@ Building linux/mips64 ..."
cd "$OLD"
......@@ -128,6 +166,8 @@ build_linux_mips64le_static() {
download_pcap
xcompile_pcap 'mips64el' 'mips64el-linux-gnuabi64' 'mips64el-linux-gnuabi64-gcc'
download_libusb
xcompile_libusb 'mips64el' 'mips64el-linux-gnuabi64' 'mips64el-linux-gnuabi64-gcc'
echo "@ Building linux/mips64le ..."
cd "$OLD"
......@@ -184,14 +224,14 @@ cd $BUILD_FOLDER
build_linux_amd64 && create_archive bettercap_linux_amd64_$VERSION.zip
build_macos_amd64 && create_archive bettercap_macos_amd64_$VERSION.zip
build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
build_windows_amd64 && create_exe_archive bettercap_windows_amd64_$VERSION.zip
build_linux_arm7_static && create_archive bettercap_linux_arm7_$VERSION.zip
# build_linux_arm7hf_static && create_archive bettercap_linux_arm7hf_$VERSION.zip
build_linux_mips_static && create_archive bettercap_linux_mips_$VERSION.zip
build_linux_mipsle_static && create_archive bettercap_linux_mipsle_$VERSION.zip
build_linux_mips64_static && create_archive bettercap_linux_mips64_$VERSION.zip
build_linux_mips64le_static && create_archive bettercap_linux_mips64le_$VERSION.zip
#build_android_arm && create_archive bettercap_android_arm_$VERSION.zip
#build_linux_arm7_static && create_archive bettercap_linux_arm7_$VERSION.zip
#build_linux_arm7hf_static && create_archive bettercap_linux_arm7hf_$VERSION.zip
#build_linux_mips_static && create_archive bettercap_linux_mips_$VERSION.zip
#build_linux_mipsle_static && create_archive bettercap_linux_mipsle_$VERSION.zip
#build_linux_mips64_static && create_archive bettercap_linux_mips64_$VERSION.zip
#build_linux_mips64le_static && create_archive bettercap_linux_mips64le_$VERSION.zip
sha256sum * > checksums.txt
echo
......
......@@ -2,7 +2,7 @@ package core
const (
Name = "bettercap"
Version = "2.17"
Version = "2.18"
Author = "Simone 'evilsocket' Margaritelli"
Website = "https://bettercap.org/"
)
......@@ -27,6 +27,13 @@ func UniqueInts(a []int, sorted bool) []int {
return uniq
}
func HasBinary(executable string) bool {
if path, err := exec.LookPath(executable); err != nil || path == "" {
return false
}
return true
}
func ExecSilent(executable string, args []string) (string, error) {
path, err := exec.LookPath(executable)
if err != nil {
......
......@@ -176,6 +176,8 @@ func (mod *RestAPI) Configure() error {
router.HandleFunc("/api/session", mod.sessionRoute)
router.HandleFunc("/api/session/ble", mod.sessionRoute)
router.HandleFunc("/api/session/ble/{mac}", mod.sessionRoute)
router.HandleFunc("/api/session/hid", mod.sessionRoute)
router.HandleFunc("/api/session/hid/{mac}", mod.sessionRoute)
router.HandleFunc("/api/session/env", mod.sessionRoute)
router.HandleFunc("/api/session/gateway", mod.sessionRoute)
router.HandleFunc("/api/session/interface", mod.sessionRoute)
......
......@@ -61,7 +61,7 @@ func (mod *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I)
}
func (mod *RestAPI) showBle(w http.ResponseWriter, r *http.Request) {
func (mod *RestAPI) showBLE(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
mac := strings.ToLower(params["mac"])
......@@ -74,6 +74,19 @@ func (mod *RestAPI) showBle(w http.ResponseWriter, r *http.Request) {
}
}
func (mod *RestAPI) showHID(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
mac := strings.ToLower(params["mac"])
if mac == "" {
mod.toJSON(w, session.I.HID)
} else if dev, found := session.I.HID.Get(mac); found {
mod.toJSON(w, dev)
} else {
http.Error(w, "Not Found", 404)
}
}
func (mod *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Env)
}
......@@ -90,7 +103,7 @@ func (mod *RestAPI) showModules(w http.ResponseWriter, r *http.Request) {
mod.toJSON(w, session.I.Modules)
}
func (mod *RestAPI) showLan(w http.ResponseWriter, r *http.Request) {
func (mod *RestAPI) showLAN(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
mac := strings.ToLower(params["mac"])
......@@ -212,7 +225,7 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
mod.showModules(w, r)
case strings.HasPrefix(path, "/api/session/lan"):
mod.showLan(w, r)
mod.showLAN(w, r)
case path == "/api/session/options":
mod.showOptions(w, r)
......@@ -224,7 +237,10 @@ func (mod *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
mod.showStartedAt(w, r)
case strings.HasPrefix(path, "/api/session/ble"):
mod.showBle(w, r)
mod.showBLE(w, r)
case strings.HasPrefix(path, "/api/session/hid"):
mod.showHID(w, r)
case strings.HasPrefix(path, "/api/session/wifi"):
mod.showWiFi(w, r)
......
......@@ -130,7 +130,7 @@ func (mod *BLERecon) Configure() (err error) {
if mod.Running() {
return session.ErrAlreadyStarted
} else if mod.gattDevice == nil {
mod.Info("initializing device ...")
mod.Debug("initializing device ...")
// hey Paypal GATT library, could you please just STFU?!
golog.SetOutput(ioutil.Discard)
......
......@@ -8,7 +8,7 @@ import (
)
func (mod *BLERecon) onStateChanged(dev gatt.Device, s gatt.State) {
mod.Info("state changed to %v", s)
mod.Debug("state changed to %v", s)
switch s {
case gatt.StatePoweredOn:
......
......@@ -38,7 +38,7 @@ func (mod *BLERecon) getRow(dev *network.BLEDevice, withName bool) []string {
return []string{
rssi,
address,
dev.Name(),
tui.Yellow(dev.Name()),
vendor,
dev.Advertisement.Flags.String(),
isConnectable,
......
......@@ -176,30 +176,30 @@ func parseProperties(ch *gatt.Characteristic) (props []string, isReadable bool,
mask := ch.Properties()
if (mask & gatt.CharBroadcast) != 0 {
props = append(props, "bcast")
props = append(props, "BCAST")
}
if (mask & gatt.CharRead) != 0 {
isReadable = true
props = append(props, "read")
props = append(props, "READ")
}
if (mask&gatt.CharWriteNR) != 0 || (mask&gatt.CharWrite) != 0 {
props = append(props, tui.Bold("write"))
props = append(props, tui.Bold("WRITE"))
isWritable = true
withResponse = (mask & gatt.CharWriteNR) == 0
}
if (mask & gatt.CharNotify) != 0 {
props = append(props, "notify")
props = append(props, "NOTIFY")
}
if (mask & gatt.CharIndicate) != 0 {
props = append(props, "indicate")
props = append(props, "INDICATE")
}
if (mask & gatt.CharSignedWrite) != 0 {
props = append(props, tui.Yellow("*write"))
props = append(props, tui.Yellow("SIGN WRITE"))
isWritable = true
withResponse = true
}
if (mask & gatt.CharExtended) != 0 {
props = append(props, "x")
props = append(props, "X")
}
return
......@@ -208,16 +208,65 @@ func parseProperties(ch *gatt.Characteristic) (props []string, isReadable bool,
func parseRawData(raw []byte) string {
s := ""
for _, b := range raw {
if b != 00 && !strconv.IsPrint(rune(b)) {
return fmt.Sprintf("%x", raw)
} else if b == 0 {
break
if strconv.IsPrint(rune(b)) {
s += tui.Yellow(string(b))
} else {
s += fmt.Sprintf("%c", b)
s += tui.Dim(fmt.Sprintf("%02x", b))
}
}
return s
}
// org.bluetooth.characteristic.gap.appearance
func parseAppearance(raw []byte) string {
app := binary.LittleEndian.Uint16(raw[0:2])
if appName, found := appearances[app]; found {
return tui.Green(appName)
}
return fmt.Sprintf("0x%x", app)
}
// org.bluetooth.characteristic.pnp_id
func parsePNPID(raw []byte) []string {
vendorIdSrc := byte(raw[0])
vendorId := binary.LittleEndian.Uint16(raw[1:3])
prodId := binary.LittleEndian.Uint16(raw[3:5])
prodVer := binary.LittleEndian.Uint16(raw[5:7])
src := ""
if vendorIdSrc == 1 {
src = " (Bluetooth SIG assigned Company Identifier)"
} else if vendorIdSrc == 2 {
src = " (USB Implementer’s Forum assigned Vendor ID value)"
}
return []string{
tui.Green("Vendor ID") + fmt.Sprintf(": 0x%04x%s", vendorId, tui.Dim(src)),
tui.Green("Product ID") + fmt.Sprintf(": 0x%04x", prodId),
tui.Green("Product Version") + fmt.Sprintf(": 0x%04x", prodVer),
}
}
// org.bluetooth.characteristic.gap.peripheral_preferred_connection_parameters
func parseConnectionParams(raw []byte) []string {
minConInt := binary.LittleEndian.Uint16(raw[0:2])
maxConInt := binary.LittleEndian.Uint16(raw[2:4])
slaveLat := binary.LittleEndian.Uint16(raw[4:6])
conTimeMul := binary.LittleEndian.Uint16(raw[6:8])
return tui.Yellow(s)
return []string{
tui.Green("Connection Interval") + fmt.Sprintf(": %d -> %d", minConInt, maxConInt),
tui.Green("Slave Latency") + fmt.Sprintf(": %d", slaveLat),
tui.Green("Connection Supervision Timeout Multiplier") + fmt.Sprintf(": %d", conTimeMul),
}
}
// org.bluetooth.characteristic.gap.peripheral_privacy_flag
func parsePrivacyFlag(raw []byte) string {
if raw[0] == 0x0 {
return tui.Green("Privacy Disabled")
}
return tui.Red("Privacy Enabled")
}
func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
......@@ -272,39 +321,60 @@ func (mod *BLERecon) showServices(p gatt.Peripheral, services []*gatt.Service) {
mod.Warning("attempt to write %d bytes to non writable characteristics %s ...", len(mod.writeData), mod.writeUUID)
}
err := p.WriteCharacteristic(ch, mod.writeData, !withResponse)
if err != nil {
if err := p.WriteCharacteristic(ch, mod.writeData, !withResponse); err != nil {
mod.Error("error while writing: %s", err)
}
}
data := ""
sz := 0
raw := ([]byte)(nil)
err := error(nil)
if isReadable {
if raw, err = p.ReadCharacteristic(ch); err != nil {
data = tui.Red(err.Error())
} else {
data = parseRawData(raw)
if raw, err = p.ReadCharacteristic(ch); raw != nil {
sz = len(raw)
}
}
if ch.Name() == "Appearance" && raw != nil && len(raw) >= 2 {
app := binary.LittleEndian.Uint16(raw[0:2])
if appName, found := appearances[app]; found {
data = tui.Green(appName)
}
data := ""
multi := ([]string)(nil)
if err != nil {
data = tui.Red(err.Error())
} else if ch.Name() == "Appearance" && sz >= 2 {
data = parseAppearance(raw)
} else if ch.Name() == "PnP ID" && sz >= 7 {
multi = parsePNPID(raw)
} else if ch.Name() == "Peripheral Preferred Connection Parameters" && sz >= 8 {
multi = parseConnectionParams(raw)
} else if ch.Name() == "Peripheral Privacy Flag" && sz >= 1 {
data = parsePrivacyFlag(raw)
} else {
data = parseRawData(raw)
}
row := []string{
fmt.Sprintf("%04x", ch.Handle()),
name,
strings.Join(props, ", "),
data,
if multi == nil {
rows = append(rows, []string{
fmt.Sprintf("%04x", ch.VHandle()),
name,
strings.Join(props, ", "),
data,
})
} else {
for i, m := range multi {
if i == 0 {
rows = append(rows, []string{
fmt.Sprintf("%04x", ch.VHandle()),
name,
strings.Join(props, ", "),
m,
})
} else {
rows = append(rows, []string{"", "", "", m})
}
}
}
rows = append(rows, row)
}
// blank row after every service, bleah style
rows = append(rows, []string{"", "", "", ""})
}
if wantsToWrite && !foundToWrite {
......
......@@ -29,6 +29,7 @@ type EventsStream struct {
output *os.File
rotation rotation
ignoreList *IgnoreList
triggerList *TriggerList
waitFor string
waitChan chan *session.Event
eventListener <-chan session.Event
......@@ -45,6 +46,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
waitChan: make(chan *session.Event),
waitFor: "",
ignoreList: NewIgnoreList(),
triggerList: NewTriggerList(),
}
mod.AddHandler(session.NewModuleHandler("events.stream on", "",
......@@ -70,6 +72,38 @@ func NewEventsStream(s *session.Session) *EventsStream {
return mod.Show(limit)
}))
on := session.NewModuleHandler("events.on TAG COMMANDS", `events\.on ([^\s]+) (.+)`,
"Run COMMANDS when an event with the specified TAG is triggered.",
func(args []string) error {
return mod.addTrigger(args[0], args[1])
})
on.Complete("events.on", s.EventsCompleter)
mod.AddHandler(on)
mod.AddHandler(session.NewModuleHandler("events.triggers", "",
"Show the list of event triggers created by the events.on command.",
func(args []string) error {
return mod.showTriggers()
}))
onClear := session.NewModuleHandler("events.trigger.delete TRIGGER_ID", `events\.trigger\.delete ([^\s]+)`,
"Remove an event trigger given its TRIGGER_ID (use events.triggers to see the list of triggers).",
func(args []string) error {
return mod.clearTrigger(args[0])
})
onClear.Complete("events.trigger.delete", mod.triggerList.Completer)
mod.AddHandler(onClear)
mod.AddHandler(session.NewModuleHandler("events.triggers.clear", "",
"Remove all event triggers (use events.triggers to see the list of triggers).",
func(args []string) error {
return mod.clearTrigger("")
}))