Compare commits

..

2 Commits
master ... tui

Author SHA1 Message Date
Your Name
3adc382eeb gonna squash this 2021-05-09 21:02:29 +00:00
Your Name
ee669871b8 adding base tui code for backup 2021-05-09 02:28:00 +00:00
26 changed files with 274 additions and 6454 deletions

7
.gitignore vendored

@ -1,6 +1,3 @@
*.code-workspace
protomol.code-workspace
.vscode/launch.json
.idea/
*.json
.eros-data/*
.logs/*
*.txt

@ -1,22 +0,0 @@
.DEFAULT_GOAL := protomolecule
.PHONY = fmt vet all clean deps benchmark
all: main
protomolecule:
go build -ldflags="-s -w -X main.GitVersion=git-$(shell git rev-list --count HEAD)-$(shell git rev-parse --short HEAD) -X main.BuildDate=$(shell date -u +%Y-%m-%dT%H:%M:%S%Z)"
install:
cp protomolecule /usr/local/bin/
uninstall:
rm /usr/local/bin/protomolecule
benchmark:
go test -bench=.
deps:
go mod tidy -v
fmt:
find . -name "*.go" -exec gofmt -s -w {} \;
vet:
go vet ./
clean:
rm protomolecule

@ -1,481 +0,0 @@
{"ManufUUID":"0xFEFF" , "ManufString":"GN Netcom","ManuFSite":""}
{"ManufUUID":"0xFEFE" , "ManufString":"GN ReSound A/S","ManuFSite":""}
{"ManufUUID":"0xFEFD" , "ManufString":"Gimbal, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEFC" , "ManufString":"Gimbal, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEFB" , "ManufString":"Telit Wireless Solutions (Formerly Stollmann E+V GmbH) ","ManuFSite":""}
{"ManufUUID":"0xFEFA" , "ManufString":"PayPal, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEF9" , "ManufString":"PayPal, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEF8" , "ManufString":"Aplix Corporation","ManuFSite":""}
{"ManufUUID":"0xFEF7" , "ManufString":"Aplix Corporation","ManuFSite":""}
{"ManufUUID":"0xFEF6" , "ManufString":"Wicentric, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEF5" , "ManufString":"Dialog Semiconductor GmbH","ManuFSite":""}
{"ManufUUID":"0xFEF4" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFEF3" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFEF2" , "ManufString":"CSR","ManuFSite":""}
{"ManufUUID":"0xFEF1" , "ManufString":"CSR","ManuFSite":""}
{"ManufUUID":"0xFEF0" , "ManufString":"Intel","ManuFSite":""}
{"ManufUUID":"0xFEEF" , "ManufString":"Polar Electro Oy","ManuFSite":""}
{"ManufUUID":"0xFEEE" , "ManufString":"Polar Electro Oy","ManuFSite":""}
{"ManufUUID":"0xFEED" , "ManufString":"Tile, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEEC" , "ManufString":"Tile, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEEB" , "ManufString":"Swirl Networks, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEEA" , "ManufString":"Swirl Networks, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEE9" , "ManufString":"Quintic Corp.","ManuFSite":""}
{"ManufUUID":"0xFEE8" , "ManufString":"Quintic Corp.","ManuFSite":""}
{"ManufUUID":"0xFEE7" , "ManufString":"Tencent Holdings Limited.","ManuFSite":""}
{"ManufUUID":"0xFEE6" , "ManufString":"Silvair, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEE5" , "ManufString":"Nordic Semiconductor ASA","ManuFSite":""}
{"ManufUUID":"0xFEE4" , "ManufString":"Nordic Semiconductor ASA","ManuFSite":""}
{"ManufUUID":"0xFEE3" , "ManufString":"Anki, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEE2" , "ManufString":"Anki, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEE1" , "ManufString":"Anhui Huami Information Technology Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFEE0" , "ManufString":"Anhui Huami Information Technology Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFEDF" , "ManufString":"Design SHIFT","ManuFSite":""}
{"ManufUUID":"0xFEDE" , "ManufString":"Coin, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEDD" , "ManufString":"Jawbone","ManuFSite":""}
{"ManufUUID":"0xFEDC" , "ManufString":"Jawbone","ManuFSite":""}
{"ManufUUID":"0xFEDB" , "ManufString":"Perka, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEDA" , "ManufString":"ISSC Technologies Corp.","ManuFSite":""}
{"ManufUUID":"0xFED9" , "ManufString":"Pebble Technology Corporation","ManuFSite":""}
{"ManufUUID":"0xFED8" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFED7" , "ManufString":"Broadcom","ManuFSite":""}
{"ManufUUID":"0xFED6" , "ManufString":"Broadcom","ManuFSite":""}
{"ManufUUID":"0xFED5" , "ManufString":"Plantronics Inc.","ManuFSite":""}
{"ManufUUID":"0xFED4" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFED3" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFED2" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFED1" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFED0" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFECF" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFECE" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFECD" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFECC" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFECB" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFECA" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEC9" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEC8" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEC7" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEC6" , "ManufString":"Kocomojo, LLC","ManuFSite":""}
{"ManufUUID":"0xFEC5" , "ManufString":"Realtek Semiconductor Corp.","ManuFSite":""}
{"ManufUUID":"0xFEC4" , "ManufString":"PLUS Location Systems","ManuFSite":""}
{"ManufUUID":"0xFEC3" , "ManufString":"360fly, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEC2" , "ManufString":"Blue Spark Technologies, Inc. ","ManuFSite":""}
{"ManufUUID":"0xFEC1" , "ManufString":"KDDI Corporation","ManuFSite":""}
{"ManufUUID":"0xFEC0" , "ManufString":"KDDI Corporation","ManuFSite":""}
{"ManufUUID":"0xFEBF" , "ManufString":"Nod, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEBE" , "ManufString":"Bose Corporation","ManuFSite":""}
{"ManufUUID":"0xFEBD" , "ManufString":"Clover Network, Inc","ManuFSite":""}
{"ManufUUID":"0xFEBC" , "ManufString":"Dexcom Inc","ManuFSite":""}
{"ManufUUID":"0xFEBB" , "ManufString":"adafruit industries","ManuFSite":""}
{"ManufUUID":"0xFEBA" , "ManufString":"Tencent Holdings Limited","ManuFSite":""}
{"ManufUUID":"0xFEB9" , "ManufString":"LG Electronics","ManuFSite":""}
{"ManufUUID":"0xFEB8" , "ManufString":"Facebook, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEB7" , "ManufString":"Facebook, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEB6" , "ManufString":"Vencer Co., Ltd","ManuFSite":""}
{"ManufUUID":"0xFEB5" , "ManufString":"WiSilica Inc.","ManuFSite":""}
{"ManufUUID":"0xFEB4" , "ManufString":"WiSilica Inc.","ManuFSite":""}
{"ManufUUID":"0xFEB3" , "ManufString":"Taobao","ManuFSite":""}
{"ManufUUID":"0xFEB2" , "ManufString":"Microsoft Corporation","ManuFSite":""}
{"ManufUUID":"0xFEB1" , "ManufString":"Electronics Tomorrow Limited","ManuFSite":""}
{"ManufUUID":"0xFEB0" , "ManufString":"Nest Labs Inc","ManuFSite":""}
{"ManufUUID":"0xFEAF" , "ManufString":"Nest Labs Inc","ManuFSite":""}
{"ManufUUID":"0xFEAE" , "ManufString":"Nokia","ManuFSite":""}
{"ManufUUID":"0xFEAD" , "ManufString":"Nokia","ManuFSite":""}
{"ManufUUID":"0xFEAC" , "ManufString":"Nokia","ManuFSite":""}
{"ManufUUID":"0xFEAB" , "ManufString":"Nokia","ManuFSite":""}
{"ManufUUID":"0xFEAA" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFEA9" , "ManufString":"Savant Systems LLC","ManuFSite":""}
{"ManufUUID":"0xFEA8" , "ManufString":"Savant Systems LLC","ManuFSite":""}
{"ManufUUID":"0xFEA7" , "ManufString":"UTC Fire and Security ","ManuFSite":""}
{"ManufUUID":"0xFEA6" , "ManufString":"GoPro, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEA5" , "ManufString":"GoPro, Inc.","ManuFSite":""}
{"ManufUUID":"0xFEA4" , "ManufString":"Paxton Access Ltd","ManuFSite":""}
{"ManufUUID":"0xFEA3" , "ManufString":"ITT Industries","ManuFSite":""}
{"ManufUUID":"0xFEA2" , "ManufString":"Intrepid Control Systems, Inc. ","ManuFSite":""}
{"ManufUUID":"0xFEA1" , "ManufString":"Intrepid Control Systems, Inc. ","ManuFSite":""}
{"ManufUUID":"0xFEA0" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFE9F" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFE9E" , "ManufString":"Dialog Semiconductor B.V.","ManuFSite":""}
{"ManufUUID":"0xFE9D" , "ManufString":"Mobiquity Networks Inc","ManuFSite":""}
{"ManufUUID":"0xFE9C" , "ManufString":"GSI Laboratories, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE9B" , "ManufString":"Samsara Networks, Inc","ManuFSite":""}
{"ManufUUID":"0xFE9A" , "ManufString":"Estimote","ManuFSite":""}
{"ManufUUID":"0xFE99" , "ManufString":"Currant Inc","ManuFSite":""}
{"ManufUUID":"0xFE98" , "ManufString":"Currant Inc","ManuFSite":""}
{"ManufUUID":"0xFE97" , "ManufString":"Tesla Motors Inc.","ManuFSite":""}
{"ManufUUID":"0xFE96" , "ManufString":"Tesla Motors Inc.","ManuFSite":""}
{"ManufUUID":"0xFE95" , "ManufString":"Xiaomi Inc.","ManuFSite":""}
{"ManufUUID":"0xFE94" , "ManufString":"OttoQ In","ManuFSite":""}
{"ManufUUID":"0xFE93" , "ManufString":"OttoQ In","ManuFSite":""}
{"ManufUUID":"0xFE92" , "ManufString":"Jarden Safety & Security ","ManuFSite":""}
{"ManufUUID":"0xFE91" , "ManufString":"Shanghai Imilab Technology Co.,Ltd ","ManuFSite":""}
{"ManufUUID":"0xFE90" , "ManufString":"JUMA","ManuFSite":""}
{"ManufUUID":"0xFE8F" , "ManufString":"CSR","ManuFSite":""}
{"ManufUUID":"0xFE8E" , "ManufString":"ARM Ltd","ManuFSite":""}
{"ManufUUID":"0xFE8D" , "ManufString":"Interaxon Inc.","ManuFSite":""}
{"ManufUUID":"0xFE8C" , "ManufString":"TRON Forum","ManuFSite":""}
{"ManufUUID":"0xFE8B" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE8A" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE89" , "ManufString":"B&O Play A/S","ManuFSite":""}
{"ManufUUID":"0xFE88" , "ManufString":"SALTO SYSTEMS S.L.","ManuFSite":""}
{"ManufUUID":"0xFE87" , "ManufString":"Qingdao Yeelink Information Technology Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFE86" , "ManufString":"HUAWEI Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFE85" , "ManufString":"RF Digital Corp","ManuFSite":""}
{"ManufUUID":"0xFE84" , "ManufString":"RF Digital Corp","ManuFSite":""}
{"ManufUUID":"0xFE83" , "ManufString":"Blue Bite","ManuFSite":""}
{"ManufUUID":"0xFE82" , "ManufString":"Medtronic Inc.","ManuFSite":""}
{"ManufUUID":"0xFE81" , "ManufString":"Medtronic Inc.","ManuFSite":""}
{"ManufUUID":"0xFE80" , "ManufString":"Doppler Lab","ManuFSite":""}
{"ManufUUID":"0xFE7F" , "ManufString":"Doppler Lab","ManuFSite":""}
{"ManufUUID":"0xFE7E" , "ManufString":"Awear Solutions Ltd","ManuFSite":""}
{"ManufUUID":"0xFE7D" , "ManufString":"Aterica Health Inc.","ManuFSite":""}
{"ManufUUID":"0xFE7C" , "ManufString":"Telit Wireless Solutions (Formerly Stollmann E+V GmbH) ","ManuFSite":""}
{"ManufUUID":"0xFE7B" , "ManufString":"Orion Labs, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE7A" , "ManufString":"Bragi GmbH","ManuFSite":""}
{"ManufUUID":"0xFE79" , "ManufString":"Zebra Technologies","ManuFSite":""}
{"ManufUUID":"0xFE78" , "ManufString":"Hewlett-Packard Company","ManuFSite":""}
{"ManufUUID":"0xFE77" , "ManufString":"Hewlett-Packard Company","ManuFSite":""}
{"ManufUUID":"0xFE76" , "ManufString":"TangoMe","ManuFSite":""}
{"ManufUUID":"0xFE75" , "ManufString":"TangoMe","ManuFSite":""}
{"ManufUUID":"0xFE74" , "ManufString":"unwire","ManuFSite":""}
{"ManufUUID":"0xFE73" , "ManufString":"Abbott (formerly St. Jude Medical, Inc.)","ManuFSite":""}
{"ManufUUID":"0xFE72" , "ManufString":"Abbott (formerly St. Jude Medical, Inc.)","ManuFSite":""}
{"ManufUUID":"0xFE71" , "ManufString":"Plume Design Inc","ManuFSite":""}
{"ManufUUID":"0xFE70" , "ManufString":"Beijing Jingdong Century Trading Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFE6F" , "ManufString":"LINE Corporation","ManuFSite":""}
{"ManufUUID":"0xFE6E" , "ManufString":"The University of Tokyo ","ManuFSite":""}
{"ManufUUID":"0xFE6D" , "ManufString":"The University of Tokyo ","ManuFSite":""}
{"ManufUUID":"0xFE6C" , "ManufString":"TASER International, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE6B" , "ManufString":"TASER International, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE6A" , "ManufString":"Kontakt Micro-Location Sp. z o.o.","ManuFSite":""}
{"ManufUUID":"0xFE69" , "ManufString":"Qualcomm Life Inc","ManuFSite":""}
{"ManufUUID":"0xFE68" , "ManufString":"Qualcomm Life Inc","ManuFSite":""}
{"ManufUUID":"0xFE67" , "ManufString":"Lab Sensor Solutions","ManuFSite":""}
{"ManufUUID":"0xFE66" , "ManufString":"Intel Corporation","ManuFSite":""}
{"ManufUUID":"0xFE65" , "ManufString":"CHIPOLO d.o.o.","ManuFSite":""}
{"ManufUUID":"0xFE64" , "ManufString":"Siemens AG","ManuFSite":""}
{"ManufUUID":"0xFE63" , "ManufString":"Connected Yard, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE62" , "ManufString":"Indagem Tech LLC","ManuFSite":""}
{"ManufUUID":"0xFE61" , "ManufString":"Logitech International SA","ManuFSite":""}
{"ManufUUID":"0xFE60" , "ManufString":"Lierda Science & Technology Group Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFE5F" , "ManufString":"Eyefi, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE5E" , "ManufString":"Plastc Corporation","ManuFSite":""}
{"ManufUUID":"0xFE5D" , "ManufString":"Grundfos A/S","ManuFSite":""}
{"ManufUUID":"0xFE5C" , "ManufString":"million hunters GmbH","ManuFSite":""}
{"ManufUUID":"0xFE5B" , "ManufString":"GT-tronics HK Ltd","ManuFSite":""}
{"ManufUUID":"0xFE5A" , "ManufString":"Cronologics Corporation","ManuFSite":""}
{"ManufUUID":"0xFE59" , "ManufString":"Nordic Semiconductor ASA","ManuFSite":""}
{"ManufUUID":"0xFE58" , "ManufString":"Nordic Semiconductor ASA","ManuFSite":""}
{"ManufUUID":"0xFE57" , "ManufString":"Dotted Labs","ManuFSite":""}
{"ManufUUID":"0xFE56" , "ManufString":"Google Inc.","ManuFSite":""}
{"ManufUUID":"0xFE55" , "ManufString":"Google Inc.","ManuFSite":""}
{"ManufUUID":"0xFE54" , "ManufString":"Motiv, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE53" , "ManufString":"3M","ManuFSite":""}
{"ManufUUID":"0xFE52" , "ManufString":"SetPoint Medical","ManuFSite":""}
{"ManufUUID":"0xFE51" , "ManufString":"SRAM","ManuFSite":""}
{"ManufUUID":"0xFE50" , "ManufString":"Google Inc.","ManuFSite":""}
{"ManufUUID":"0xFE4F" , "ManufString":"Molekule, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE4E" , "ManufString":"NTT docomo","ManuFSite":""}
{"ManufUUID":"0xFE4D" , "ManufString":"Casambi Technologies Oy","ManuFSite":""}
{"ManufUUID":"0xFE4C" , "ManufString":"Volkswagen AG","ManuFSite":""}
{"ManufUUID":"0xFE4B" , "ManufString":"Signify Netherlands B.V. (formerly Philips Lighting B.V.) ","ManuFSite":""}
{"ManufUUID":"0xFE4A" , "ManufString":"OMRON HEALTHCARE Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFE49" , "ManufString":"SenionLab AB","ManuFSite":""}
{"ManufUUID":"0xFE48" , "ManufString":"General Motors","ManuFSite":""}
{"ManufUUID":"0xFE47" , "ManufString":"General Motors","ManuFSite":""}
{"ManufUUID":"0xFE46" , "ManufString":"B&O Play A/S","ManuFSite":""}
{"ManufUUID":"0xFE45" , "ManufString":"Snapchat Inc","ManuFSite":""}
{"ManufUUID":"0xFE44" , "ManufString":"SK Telecom","ManuFSite":""}
{"ManufUUID":"0xFE43" , "ManufString":"Andreas Stihl AG & Co. KG","ManuFSite":""}
{"ManufUUID":"0xFE42" , "ManufString":"Nets A/S","ManuFSite":""}
{"ManufUUID":"0xFE41" , "ManufString":"Inugo Systems Limited","ManuFSite":""}
{"ManufUUID":"0xFE40" , "ManufString":"Inugo Systems Limited","ManuFSite":""}
{"ManufUUID":"0xFE3F" , "ManufString":"Friday Labs Limited","ManuFSite":""}
{"ManufUUID":"0xFE3E" , "ManufString":"BD Medical","ManuFSite":""}
{"ManufUUID":"0xFE3D" , "ManufString":"BD Medical","ManuFSite":""}
{"ManufUUID":"0xFE3C" , "ManufString":"alibaba","ManuFSite":""}
{"ManufUUID":"0xFE3B" , "ManufString":"Dolby Laboratories","ManuFSite":""}
{"ManufUUID":"0xFE3A" , "ManufString":"TTS Tooltechnic Systems AG & Co. KG ","ManuFSite":""}
{"ManufUUID":"0xFE39" , "ManufString":"TTS Tooltechnic Systems AG & Co. KG ","ManuFSite":""}
{"ManufUUID":"0xFE38" , "ManufString":"Spaceek LTD","ManuFSite":""}
{"ManufUUID":"0xFE37" , "ManufString":"Spaceek LTD","ManuFSite":""}
{"ManufUUID":"0xFE36" , "ManufString":"HUAWEI Technologies Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFE35" , "ManufString":"HUAWEI Technologies Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFE34" , "ManufString":"SmallLoop LLC","ManuFSite":""}
{"ManufUUID":"0xFE33" , "ManufString":"CHIPOLO d.o.o.","ManuFSite":""}
{"ManufUUID":"0xFE32" , "ManufString":"Pro-Mark, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE31" , "ManufString":"Volkswagen AG","ManuFSite":""}
{"ManufUUID":"0xFE30" , "ManufString":"Volkswagen AG","ManuFSite":""}
{"ManufUUID":"0xFE2F" , "ManufString":"CRESCO Wireless, Inc","ManuFSite":""}
{"ManufUUID":"0xFE2E" , "ManufString":"ERi,Inc.","ManuFSite":""}
{"ManufUUID":"0xFE2D" , "ManufString":"SMART INNOVATION Co.,Ltd","ManuFSite":""}
{"ManufUUID":"0xFE2C" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFE2B" , "ManufString":"ITT Industries","ManuFSite":""}
{"ManufUUID":"0xFE2A" , "ManufString":"DaisyWorks, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE29" , "ManufString":"Gibson Innovations","ManuFSite":""}
{"ManufUUID":"0xFE28" , "ManufString":"Ayla Networks","ManuFSite":""}
{"ManufUUID":"0xFE27" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFE26" , "ManufString":"Google","ManuFSite":""}
{"ManufUUID":"0xFE25" , "ManufString":"Apple, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE24" , "ManufString":"August Home Inc","ManuFSite":""}
{"ManufUUID":"0xFE23" , "ManufString":"Zoll Medical Corporation","ManuFSite":""}
{"ManufUUID":"0xFE22" , "ManufString":"Zoll Medical Corporation","ManuFSite":""}
{"ManufUUID":"0xFE21" , "ManufString":"Bose Corporation","ManuFSite":""}
{"ManufUUID":"0xFE20" , "ManufString":"Emerson","ManuFSite":""}
{"ManufUUID":"0xFE1F" , "ManufString":"Garmin International, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE1E" , "ManufString":"Smart Innovations Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFE1D" , "ManufString":"Illuminati Instrument Corporation","ManuFSite":""}
{"ManufUUID":"0xFE1C" , "ManufString":"NetMedia, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE1B" , "ManufString":"Tyto Life LLC","ManuFSite":""}
{"ManufUUID":"0xFE1A" , "ManufString":"Tyto Life LLC","ManuFSite":""}
{"ManufUUID":"0xFE19" , "ManufString":"Google, Inc","ManuFSite":""}
{"ManufUUID":"0xFE18" , "ManufString":"Runtime, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE17" , "ManufString":"Telit Wireless Solutions GmbH ","ManuFSite":""}
{"ManufUUID":"0xFE16" , "ManufString":"Footmarks, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE15" , "ManufString":"Amazon.com Services, Inc..","ManuFSite":""}
{"ManufUUID":"0xFE14" , "ManufString":"Flextronics International USA Inc. ","ManuFSite":""}
{"ManufUUID":"0xFE13" , "ManufString":"Apple Inc.","ManuFSite":""}
{"ManufUUID":"0xFE12" , "ManufString":"M-Way Solutions GmbH","ManuFSite":""}
{"ManufUUID":"0xFE11" , "ManufString":"GMC-I Messtechnik GmbH","ManuFSite":""}
{"ManufUUID":"0xFE10" , "ManufString":"LAPIS Technology Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFE0F" , "ManufString":"Signify Netherlands B.V. (formerly Philips Lighting B.V.) ","ManuFSite":""}
{"ManufUUID":"0xFE0E" , "ManufString":"Setec Pty Ltd","ManuFSite":""}
{"ManufUUID":"0xFE0D" , "ManufString":"Procter & Gamble","ManuFSite":""}
{"ManufUUID":"0xFE0C" , "ManufString":"Procter & Gamble","ManuFSite":""}
{"ManufUUID":"0xFE0B" , "ManufString":"ruwido austria gmbh","ManuFSite":""}
{"ManufUUID":"0xFE0A" , "ManufString":"ruwido austria gmbh","ManuFSite":""}
{"ManufUUID":"0xFE09" , "ManufString":"Pillsy, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE08" , "ManufString":"Microsoft","ManuFSite":""}
{"ManufUUID":"0xFE07" , "ManufString":"Sonos, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE06" , "ManufString":"Qualcomm Technologies, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE05" , "ManufString":"CORE Transport Technologies NZ Limited","ManuFSite":""}
{"ManufUUID":"0xFE04" , "ManufString":"OpenPath Security Inc","ManuFSite":""}
{"ManufUUID":"0xFE03" , "ManufString":"Amazon.com Services, Inc.","ManuFSite":""}
{"ManufUUID":"0xFE02" , "ManufString":"Robert Bosch GmbH","ManuFSite":""}
{"ManufUUID":"0xFE01" , "ManufString":"Duracell U.S. Operations Inc. ","ManuFSite":""}
{"ManufUUID":"0xFE00" , "ManufString":"Amazon.com Services, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDFF" , "ManufString":"OSRAM GmbH","ManuFSite":""}
{"ManufUUID":"0xFDFE" , "ManufString":"ADHERIUM(NZ) LIMITED","ManuFSite":""}
{"ManufUUID":"0xFDFD" , "ManufString":"RecursiveSoft Inc.","ManuFSite":""}
{"ManufUUID":"0xFDFC" , "ManufString":"Optrel AG","ManuFSite":""}
{"ManufUUID":"0xFDFB" , "ManufString":"Tandem Diabetes Care","ManuFSite":""}
{"ManufUUID":"0xFDFA" , "ManufString":"Tandem Diabetes Care","ManuFSite":""}
{"ManufUUID":"0xFDF9" , "ManufString":"INIA","ManuFSite":""}
{"ManufUUID":"0xFDF8" , "ManufString":"Onvocal","ManuFSite":""}
{"ManufUUID":"0xFDF7" , "ManufString":"HP Inc.","ManuFSite":""}
{"ManufUUID":"0xFDF6" , "ManufString":"AIAIAI ApS","ManuFSite":""}
{"ManufUUID":"0xFDF5" , "ManufString":"Milwaukee Electric Tools","ManuFSite":""}
{"ManufUUID":"0xFDF4" , "ManufString":"O. E. M. Controls, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDF3" , "ManufString":"Amersports","ManuFSite":""}
{"ManufUUID":"0xFDF2" , "ManufString":"AMICCOM Electronics Corporation","ManuFSite":""}
{"ManufUUID":"0xFDF1" , "ManufString":"LAMPLIGHT Co.,Ltd","ManuFSite":""}
{"ManufUUID":"0xFDF0" , "ManufString":"Google Inc.","ManuFSite":""}
{"ManufUUID":"0xFDEF" , "ManufString":"ART AND PROGRAM, INC. ","ManuFSite":""}
{"ManufUUID":"0xFDEE" , "ManufString":"Huawei Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFDED" , "ManufString":"Pole Star","ManuFSite":""}
{"ManufUUID":"0xFDEC" , "ManufString":"Mannkind Corporation","ManuFSite":""}
{"ManufUUID":"0xFDEB" , "ManufString":"Syntronix Corporation","ManuFSite":""}
{"ManufUUID":"0xFDEA" , "ManufString":"SeeScan, Inc","ManuFSite":""}
{"ManufUUID":"0xFDE9" , "ManufString":"Spacesaver Corporation","ManuFSite":""}
{"ManufUUID":"0xFDE8" , "ManufString":"Robert Bosch GmbH","ManuFSite":""}
{"ManufUUID":"0xFDE7" , "ManufString":"SECOM Co., LTD","ManuFSite":""}
{"ManufUUID":"0xFDE6" , "ManufString":"Intelletto Technologies Inc","ManuFSite":""}
{"ManufUUID":"0xFDE5" , "ManufString":"SMK Corporation","ManuFSite":""}
{"ManufUUID":"0xFDE4" , "ManufString":"JUUL Labs, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDE3" , "ManufString":"Abbott Diabetes Care","ManuFSite":""}
{"ManufUUID":"0xFDE2" , "ManufString":"Google Inc.","ManuFSite":""}
{"ManufUUID":"0xFDE1" , "ManufString":"Fortin Electronic Systems","ManuFSite":""}
{"ManufUUID":"0xFDE0" , "ManufString":"John Deere","ManuFSite":""}
{"ManufUUID":"0xFDDF" , "ManufString":"Harman International","ManuFSite":""}
{"ManufUUID":"0xFDDE" , "ManufString":"Noodle Technology Inc.","ManuFSite":""}
{"ManufUUID":"0xFDDD" , "ManufString":"Arch Systems Inc","ManuFSite":""}
{"ManufUUID":"0xFDDC" , "ManufString":"4iiii Innovations Inc.","ManuFSite":""}
{"ManufUUID":"0xFDDB" , "ManufString":"Samsung Electronics Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFDDA" , "ManufString":"MHCS","ManuFSite":""}
{"ManufUUID":"0xFDD9" , "ManufString":"Jiangsu Teranovo Tech Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFDD8" , "ManufString":"Jiangsu Teranovo Tech Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFDD7" , "ManufString":"Emerson","ManuFSite":""}
{"ManufUUID":"0xFDD6" , "ManufString":"Ministry of Supply","ManuFSite":""}
{"ManufUUID":"0xFDD5" , "ManufString":"Brompton Bicycle Ltd","ManuFSite":""}
{"ManufUUID":"0xFDD4" , "ManufString":"LX Solutions Pty Limited ","ManuFSite":""}
{"ManufUUID":"0xFDD3" , "ManufString":"FUBA Automotive Electronics GmbH ","ManuFSite":""}
{"ManufUUID":"0xFDD2" , "ManufString":"Bose Corporation","ManuFSite":""}
{"ManufUUID":"0xFDD1" , "ManufString":"Huawei Technologies Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFDD0" , "ManufString":"Huawei Technologies Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFDCF" , "ManufString":"Nalu Medical, Inc","ManuFSite":""}
{"ManufUUID":"0xFDCE" , "ManufString":"SENNHEISER electronic GmbH & Co. KG","ManuFSite":""}
{"ManufUUID":"0xFDCD" , "ManufString":"Qingping Technology (Beijing) Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFDCC" , "ManufString":"Shoof Technologies","ManuFSite":""}
{"ManufUUID":"0xFDCB" , "ManufString":"Meggitt SA","ManuFSite":""}
{"ManufUUID":"0xFDCA" , "ManufString":"Fortin Electronic Systems","ManuFSite":""}
{"ManufUUID":"0xFDC9" , "ManufString":"Busch-Jaeger Elektro GmbH","ManuFSite":""}
{"ManufUUID":"0xFDC8" , "ManufString":"Hach Danaher","ManuFSite":""}
{"ManufUUID":"0xFDC7" , "ManufString":"Eli Lilly and Company ","ManuFSite":""}
{"ManufUUID":"0xFDC6" , "ManufString":"Eli Lilly and Company ","ManuFSite":""}
{"ManufUUID":"0xFDC5" , "ManufString":"Automatic Labs","ManuFSite":""}
{"ManufUUID":"0xFDC4" , "ManufString":"Simavita (Aust) Pty Ltd ","ManuFSite":""}
{"ManufUUID":"0xFDC3" , "ManufString":"Baidu Online Network Technology (Beijing) Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFDC2" , "ManufString":"Baidu Online Network Technology (Beijing) Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFDC1" , "ManufString":"Hunter Douglas","ManuFSite":""}
{"ManufUUID":"0xFDC0" , "ManufString":"Hunter Douglas","ManuFSite":""}
{"ManufUUID":"0xFDBF" , "ManufString":"California Things Inc.","ManuFSite":""}
{"ManufUUID":"0xFDBE" , "ManufString":"California Things Inc.","ManuFSite":""}
{"ManufUUID":"0xFDBD" , "ManufString":"Clover Network, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDBC" , "ManufString":"Emerson","ManuFSite":""}
{"ManufUUID":"0xFDBB" , "ManufString":"Profoto","ManuFSite":""}
{"ManufUUID":"0xFDBA" , "ManufString":"Comcast Cable Corporation","ManuFSite":""}
{"ManufUUID":"0xFDB9" , "ManufString":"Comcast Cable Corporation","ManuFSite":""}
{"ManufUUID":"0xFDB8" , "ManufString":"LivaNova USA Inc.","ManuFSite":""}
{"ManufUUID":"0xFDB7" , "ManufString":"LivaNova USA Inc.","ManuFSite":""}
{"ManufUUID":"0xFDB6" , "ManufString":"GWA Hygiene GmbH","ManuFSite":""}
{"ManufUUID":"0xFDB5" , "ManufString":"ECSG","ManuFSite":""}
{"ManufUUID":"0xFDB4" , "ManufString":"HP Inc","ManuFSite":""}
{"ManufUUID":"0xFDB3" , "ManufString":"Audiodo AB","ManuFSite":""}
{"ManufUUID":"0xFDB2" , "ManufString":"Portable Multimedia Ltd","ManuFSite":""}
{"ManufUUID":"0xFDB1" , "ManufString":"Proxy Technologies, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDB0" , "ManufString":"Proxy Technologies, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDAF" , "ManufString":"Wiliot LTD","ManuFSite":""}
{"ManufUUID":"0xFDAE" , "ManufString":"Houwa System Design, k.k. ","ManuFSite":""}
{"ManufUUID":"0xFDAD" , "ManufString":"Houwa System Design, k.k. ","ManuFSite":""}
{"ManufUUID":"0xFDAC" , "ManufString":"Tentacle Sync GmbH","ManuFSite":""}
{"ManufUUID":"0xFDAB" , "ManufString":"Xiaomi Inc.","ManuFSite":""}
{"ManufUUID":"0xFDAA" , "ManufString":"Xiaomi Inc.","ManuFSite":""}
{"ManufUUID":"0xFDA9" , "ManufString":"Rhombus Systems, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDA8" , "ManufString":"PSA Peugeot Citroën","ManuFSite":""}
{"ManufUUID":"0xFDA7" , "ManufString":"WWZN Information Technology Company Limited","ManuFSite":""}
{"ManufUUID":"0xFDA6" , "ManufString":"WWZN Information Technology Company Limited","ManuFSite":""}
{"ManufUUID":"0xFDA5" , "ManufString":"Neurostim OAB, Inc.","ManuFSite":""}
{"ManufUUID":"0xFDA4" , "ManufString":"Inseego Corp.","ManuFSite":""}
{"ManufUUID":"0xFDA3" , "ManufString":"Inseego Corp.","ManuFSite":""}
{"ManufUUID":"0xFDA2" , "ManufString":"Groove X, Inc","ManuFSite":""}
{"ManufUUID":"0xFDA1" , "ManufString":"Groove X, Inc","ManuFSite":""}
{"ManufUUID":"0xFDA0" , "ManufString":"Secugen Corporation","ManuFSite":""}
{"ManufUUID":"0xFD9F" , "ManufString":"VitalTech Affiliates LLC","ManuFSite":""}
{"ManufUUID":"0xFD9E" , "ManufString":"The Coca-Cola Company","ManuFSite":""}
{"ManufUUID":"0xFD9D" , "ManufString":"Gastec Corporation","ManuFSite":""}
{"ManufUUID":"0xFD9C" , "ManufString":"Huawei Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD9B" , "ManufString":"Huawei Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD9A" , "ManufString":"Huawei Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD99" , "ManufString":"ABB Oy","ManuFSite":""}
{"ManufUUID":"0xFD98" , "ManufString":"Disney Worldwide Services, Inc. ","ManuFSite":""}
{"ManufUUID":"0xFD97" , "ManufString":"June Life, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD96" , "ManufString":"Google LLC","ManuFSite":""}
{"ManufUUID":"0xFD95" , "ManufString":"Rigado","ManuFSite":""}
{"ManufUUID":"0xFD94" , "ManufString":"Hewlett Packard Enterprise","ManuFSite":""}
{"ManufUUID":"0xFD93" , "ManufString":"Bayerische Motoren Werke AG ","ManuFSite":""}
{"ManufUUID":"0xFD92" , "ManufString":"Qualcomm Technologies International, Ltd. (QTIL)","ManuFSite":""}
{"ManufUUID":"0xFD91" , "ManufString":"Groove X, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD90" , "ManufString":"Guangzhou SuperSound Information Technology Co.,Ltd","ManuFSite":""}
{"ManufUUID":"0xFD8F" , "ManufString":"Matrix ComSec Pvt. Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD8E" , "ManufString":"Motorola Solutions","ManuFSite":""}
{"ManufUUID":"0xFD8D" , "ManufString":"quip NYC Inc.","ManuFSite":""}
{"ManufUUID":"0xFD8C" , "ManufString":"Google LLC","ManuFSite":""}
{"ManufUUID":"0xFD8B" , "ManufString":"Jigowatts Inc.","ManuFSite":""}
{"ManufUUID":"0xFD8A" , "ManufString":"Signify Netherlands B.V.","ManuFSite":""}
{"ManufUUID":"0xFD89" , "ManufString":"Urbanminded LTD","ManuFSite":""}
{"ManufUUID":"0xFD88" , "ManufString":"Urbanminded LTD","ManuFSite":""}
{"ManufUUID":"0xFD87" , "ManufString":"Google LLC","ManuFSite":""}
{"ManufUUID":"0xFD86" , "ManufString":"Abbott","ManuFSite":""}
{"ManufUUID":"0xFD85" , "ManufString":"Husqvarna AB","ManuFSite":""}
{"ManufUUID":"0xFD84" , "ManufString":"Tile, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD83" , "ManufString":"iNFORM Technology GmbH","ManuFSite":""}
{"ManufUUID":"0xFD82" , "ManufString":"Sony Corporation","ManuFSite":""}
{"ManufUUID":"0xFD81" , "ManufString":"CANDY HOUSE, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD80" , "ManufString":"Phindex Technologies, Inc","ManuFSite":""}
{"ManufUUID":"0xFD7F" , "ManufString":"Husqvarna AB","ManuFSite":""}
{"ManufUUID":"0xFD7E" , "ManufString":"Samsung Electronics Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD7D" , "ManufString":"Center for Advanced Research Wernher Von Braun ","ManuFSite":""}
{"ManufUUID":"0xFD7C" , "ManufString":"Toshiba Information Systems(Japan) Corporation ","ManuFSite":""}
{"ManufUUID":"0xFD7B" , "ManufString":"WYZE LABS, INC.","ManuFSite":""}
{"ManufUUID":"0xFD7A" , "ManufString":"Withings","ManuFSite":""}
{"ManufUUID":"0xFD79" , "ManufString":"Withings","ManuFSite":""}
{"ManufUUID":"0xFD78" , "ManufString":"Withings","ManuFSite":""}
{"ManufUUID":"0xFD77" , "ManufString":"Withings","ManuFSite":""}
{"ManufUUID":"0xFD76" , "ManufString":"Insulet Corporation","ManuFSite":""}
{"ManufUUID":"0xFD75" , "ManufString":"Insulet Corporation","ManuFSite":""}
{"ManufUUID":"0xFD74" , "ManufString":"BRControls Products BV","ManuFSite":""}
{"ManufUUID":"0xFD73" , "ManufString":"BRControls Products BV","ManuFSite":""}
{"ManufUUID":"0xFD72" , "ManufString":"Logitech International SA","ManuFSite":""}
{"ManufUUID":"0xFD71" , "ManufString":"GN Hearing A/S","ManuFSite":""}
{"ManufUUID":"0xFD70" , "ManufString":"GuangDong Oppo Mobile Telecommunications Corp., Ltd","ManuFSite":""}
{"ManufUUID":"0xFD6F" , "ManufString":"Apple, Inc. [iPhone]","ManuFSite":""}
{"ManufUUID":"0xFD6E" , "ManufString":"Polidea sp. z o.o. ","ManuFSite":""}
{"ManufUUID":"0xFD6D" , "ManufString":"Sigma Elektro GmbH","ManuFSite":""}
{"ManufUUID":"0xFD6C" , "ManufString":"Samsung Electronics Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD6B" , "ManufString":"rapitag GmbH","ManuFSite":""}
{"ManufUUID":"0xFD6A" , "ManufString":"Emerson","ManuFSite":""}
{"ManufUUID":"0xFD69" , "ManufString":"Samsung Electronics Co., Ltd ","ManuFSite":""}
{"ManufUUID":"0xFD68" , "ManufString":"Ubique Innovation AG","ManuFSite":""}
{"ManufUUID":"0xFD67" , "ManufString":"Montblanc Simplo GmbH","ManuFSite":""}
{"ManufUUID":"0xFD66" , "ManufString":"Zebra Technologies Corporation","ManuFSite":""}
{"ManufUUID":"0xFD65" , "ManufString":"Razer Inc.","ManuFSite":""}
{"ManufUUID":"0xFD64" , "ManufString":"INRIA","ManuFSite":""}
{"ManufUUID":"0xFD63" , "ManufString":"Fitbit, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD62" , "ManufString":"Fitbit, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD61" , "ManufString":"Arendi AG","ManuFSite":""}
{"ManufUUID":"0xFD60" , "ManufString":"Sercomm Corporation","ManuFSite":""}
{"ManufUUID":"0xFD5F" , "ManufString":"Oculus VR, LLC","ManuFSite":""}
{"ManufUUID":"0xFD5E" , "ManufString":"Tapkey GmbH","ManuFSite":""}
{"ManufUUID":"0xFD5D" , "ManufString":"maxon motor ltd.","ManuFSite":""}
{"ManufUUID":"0xFD5C" , "ManufString":"React Mobile","ManuFSite":""}
{"ManufUUID":"0xFD5B" , "ManufString":"V2SOFT INC.","ManuFSite":""}
{"ManufUUID":"0xFD5A" , "ManufString":"Samsung Electronics Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD59" , "ManufString":"Samsung Electronics Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD58" , "ManufString":"Volvo Car Corporation","ManuFSite":""}
{"ManufUUID":"0xFD57" , "ManufString":"Volvo Car Corporation","ManuFSite":""}
{"ManufUUID":"0xFD56" , "ManufString":"Resmed Ltd","ManuFSite":""}
{"ManufUUID":"0xFD55" , "ManufString":"Braveheart Wireless, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD54" , "ManufString":"Qingdao Haier Technology Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFD53" , "ManufString":"PCI Private Limited","ManuFSite":""}
{"ManufUUID":"0xFD52" , "ManufString":"UTC Fire and Security ","ManuFSite":""}
{"ManufUUID":"0xFD51" , "ManufString":"UTC Fire and Security ","ManuFSite":""}
{"ManufUUID":"0xFD50" , "ManufString":"Hangzhou Tuya Information Technology Co., Ltd","ManuFSite":""}
{"ManufUUID":"0xFD4F" , "ManufString":"Forkbeard Technologies AS","ManuFSite":""}
{"ManufUUID":"0xFD4E" , "ManufString":"70mai Co.,Ltd.","ManuFSite":""}
{"ManufUUID":"0xFD4D" , "ManufString":"70mai Co.,Ltd.","ManuFSite":""}
{"ManufUUID":"0xFD4C" , "ManufString":"Adolf Wuerth GmbH & Co KG","ManuFSite":""}
{"ManufUUID":"0xFD4B" , "ManufString":"Samsung Electronics Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD4A" , "ManufString":"Sigma Elektro GmbH","ManuFSite":""}
{"ManufUUID":"0xFD49" , "ManufString":"Panasonic Corporation","ManuFSite":""}
{"ManufUUID":"0xFD48" , "ManufString":"Geberit International AG","ManuFSite":""}
{"ManufUUID":"0xFD47" , "ManufString":"Liberty Global Inc.","ManuFSite":""}
{"ManufUUID":"0xFD46" , "ManufString":"Lemco IKE","ManuFSite":""}
{"ManufUUID":"0xFD45" , "ManufString":"GB Solution co.,Ltd","ManuFSite":""}
{"ManufUUID":"0xFD44" , "ManufString":"Apple Inc.","ManuFSite":""}
{"ManufUUID":"0xFD43" , "ManufString":"Apple Inc.","ManuFSite":""}
{"ManufUUID":"0xFD42" , "ManufString":"Globe (Jiangsu) Co.,Ltd","ManuFSite":""}
{"ManufUUID":"0xFD41" , "ManufString":"Amazon Lab126","ManuFSite":""}
{"ManufUUID":"0xFD40" , "ManufString":"Beflex Inc.","ManuFSite":""}
{"ManufUUID":"0xFD3F" , "ManufString":"Cognosos, Inc","ManuFSite":""}
{"ManufUUID":"0xFD3E" , "ManufString":"Pure Watercraft, inc.","ManuFSite":""}
{"ManufUUID":"0xFD3D" , "ManufString":"Woan Technology (Shenzhen) Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFD3C" , "ManufString":"Redline Communications Inc.","ManuFSite":""}
{"ManufUUID":"0xFD3B" , "ManufString":"Verkada Inc.","ManuFSite":""}
{"ManufUUID":"0xFD3A" , "ManufString":"Verkada Inc.","ManuFSite":""}
{"ManufUUID":"0xFD39" , "ManufString":"PREDIKTAS","ManuFSite":""}
{"ManufUUID":"0xFD38" , "ManufString":"Danfoss A/S","ManuFSite":""}
{"ManufUUID":"0xFD37" , "ManufString":"TireCheck GmbH","ManuFSite":""}
{"ManufUUID":"0xFD36" , "ManufString":"Google LLC","ManuFSite":""}
{"ManufUUID":"0xFD35" , "ManufString":"Transsion Holdings Limited","ManuFSite":""}
{"ManufUUID":"0xFD34" , "ManufString":"Aerosens LLC.","ManuFSite":""}
{"ManufUUID":"0xFD33" , "ManufString":"DashLogic, Inc.","ManuFSite":""}
{"ManufUUID":"0xFD32" , "ManufString":"Gemalto Holding BV","ManuFSite":""}
{"ManufUUID":"0xFD31" , "ManufString":"LG Electronics Inc.","ManuFSite":""}
{"ManufUUID":"0xFD30" , "ManufString":"Sesam Solutions BV","ManuFSite":""}
{"ManufUUID":"0xFD2F" , "ManufString":"Bitstrata Systems Inc.","ManuFSite":""}
{"ManufUUID":"0xFD2E" , "ManufString":"Bitstrata Systems Inc.","ManuFSite":""}
{"ManufUUID":"0xFD2D" , "ManufString":"Xiaomi Inc.","ManuFSite":""}
{"ManufUUID":"0xFD2C" , "ManufString":"The Access Technologies","ManuFSite":""}
{"ManufUUID":"0xFD2B" , "ManufString":"The Access Technologies","ManuFSite":""}
{"ManufUUID":"0xFD2A" , "ManufString":"Sony Corporation","ManuFSite":""}
{"ManufUUID":"0xFD29" , "ManufString":"Asahi Kasei Corporation","ManuFSite":""}
{"ManufUUID":"0xFD28" , "ManufString":"Julius Blum GmbH","ManuFSite":""}
{"ManufUUID":"0xFD27" , "ManufString":"i2Systems","ManuFSite":""}
{"ManufUUID":"0xFD26" , "ManufString":"Novo Nordisk A/S","ManuFSite":""}
{"ManufUUID":"0xFD25" , "ManufString":"GD Midea Air-Conditioning Equipment Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFD24" , "ManufString":"GD Midea Air-Conditioning Equipment Co., Ltd.","ManuFSite":""}
{"ManufUUID":"0xFD23" , "ManufString":"DOM Sicherheitstechnik GmbH & Co. KG","ManuFSite":""}
{"ManufUUID":"0xFD22" , "ManufString":"Huawei Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD21" , "ManufString":"Huawei Technologies Co., Ltd. ","ManuFSite":""}
{"ManufUUID":"0xFD20" , "ManufString":"GN Hearing A/S","ManuFSite":""}
{"ManufUUID":"0xFD1F" , "ManufString":"3M","ManuFSite":""}

@ -1,46 +1,37 @@
# protomolecule
Cross platform BLE scanner and logger with teeth
Cross platform BLE scanner and logger with teeth \n
***add a better description***
# Dev notes
- **dust** util functions
- **eros** found devices DB
- **phoebe** tiny go stuff
- **scanStuff** well i mean...
- **blueStuff** connect to devices / retreive service characteristics
# TODO
## Short Term Needs
- ### Scan for
- ~~Device Manufacturer Data~~
- ~~Services UUIDs~~
- Device Manufacturer Data
- Services UUIDs
- ### Go channels to handle scanning (Keep continous background scanning as an option)
- ### Output to simple menu for device target / attack selection (Bubbletea? Pterm?)
- ### ~~Implement bitcask DB to hold device info (will likely need a struct) ** eros **~~
- ~~Address (Key)~~
- ~~Device Local Name (data)~~
- ~~Device Manufacturer Data (data)~~
- ~~Services UUIDs (data)~~
- ### Implement bitcask DB to hold device info (will likely need a struct) ** eros **
- MAC (Key)
- Device Local Name (data)
- Device Manufacturer Data (data)
- Services UUIDs (data)
- ### Implement- Exploit DB
- If GATT exploits are known, present user with options
- ### Logging
- Exportable
- ### Connect to device
- ### Read / Write GATT
- ### Device selection
@ -49,15 +40,13 @@ Cross platform BLE scanner and logger with teeth
- ### GATT fuzzing
- ### Advertisment fuzzing / flooding
- ### Sweyntooth
- ### Sweyntooth
- ### Search for DFU
- If nordic chip, use DFU attack
- ### Nordic Softdevice compile with tiny go (phoebe)
- ### Nordic Softdevice compile with tiny go
- What to do?

324
cmd.go

@ -1,324 +0,0 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
cli "git.tcp.direct/Mirrors/go-prompt"
. "github.com/logrusorgru/aurora"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"protomolecule/src/eros"
// "protomolecule/src/wrath"
projVars "protomolecule/src/config"
"protomolecule/src/deimos"
)
var suggestions = []cli.Suggest{
// Main Commands
// {"scan",
// "Start and stop scans"},
{"scan start",
"Start default scan"},
{"scan start attack",
"Start scan that also attempts to connect to all discovered BLE devices"},
{"scan start tracking",
"Start continuous RSSI tracking of all BLE devices within range"},
{"eros recall mac",
"Retrieve device info from database by MAC address"},
{"eros recall all",
"Retrieve info on all devices"},
{"eros backup",
"Backup all databases to the supplied directory"},
{"arboghast start",
"Start entropic fuzzing via advertising"},
/*
{"wrath",
"attack by address"},
*/
{"exit",
"Exit ProtoMolecule"},
{"debug",
"Toggle debug messages"},
{"help",
"in-app documentation"},
}
func completer(in cli.Document) []cli.Suggest {
c := in.CurrentLineBeforeCursor()
if c == "" {
return []cli.Suggest{}
}
return cli.FilterHasPrefix(suggestions, c, true)
}
// Interpret is where we will actuall define our commands
func executor(cmd string) {
cmd = strings.TrimSpace(cmd)
var args []string
args = strings.Split(cmd, " ")
getArgs := func(args []string) string {
var ret string
for i, a := range args {
if i == 0 {
ret = a
continue
}
ret = ret + " " + a
if i != len(args)-1 {
ret = ret + " "
}
}
return ret
}
switch args[0] {
case "exit":
fallthrough
case "quit":
eros.Slumber()
os.Exit(0)
case "debug":
switch log.Logger.GetLevel() {
case zerolog.DebugLevel:
zerolog.SetGlobalLevel(zerolog.InfoLevel)
log.Info().
Msg("Debug mode turned " + Sprintf(BrightRed("OFF\n").Bold()))
case zerolog.InfoLevel:
zerolog.SetGlobalLevel(zerolog.DebugLevel)
log.Debug().
Msg("Debug mode turned " + Sprintf(BrightGreen("ON\n").Bold()))
default:
return
}
case "eros":
if len(args) < 2 {
getHelp("eros")
return
}
switch args[1] {
case "recall":
if len(args) < 3 {
getHelp("eros recall")
return
}
switch args[2] {
case "mac":
if len(args) < 4 {
getHelp("eros recall")
return
}
fromEros, err := eros.Recall(args[3])
if err != nil {
log.Error().Err(err).Str("target", args[3]).
Msg("Failed to recall MAC")
println()
return
}
log.Info().
Str("Name", fromEros.Name).
Str("Addr", fromEros.Addr).
Str("ManuF", fromEros.Manufacturer). // needs to be changed back to fromEros.Manufacturer
Int16("Current_RSSI", fromEros.RSSIlast).
Int("Service_Count", len(fromEros.Services)).
Msg("EROS_RECALL")
case "backup":
if len(args) < 3 {
getHelp("eros backup")
return
}
if err := eros.Backup(args[3]); err != nil {
log.Error().Err(err).Msg("Failed to backup databases!")
}
}
}
case "help":
if len(args) < 2 {
getHelp("meta")
return
}
getHelp(getArgs(args))
case "loadnames":
if len(args) < 2 {
log.Error().Msg("missing argument: path to json file with manufacturer UUIDs")
println()
}
projVars.GlobalConfig.ManuFile = args[1]
eros.ManufLoad()
case "adapter":
if len(args) > 1 {
switch args[1] {
case "list":
deimos.PrintBlueDevices()
case "sniff":
var (
err error
devint int
)
if len(args) < 2 {
log.Error().Msg("not enough parameters, missing adapter ID")
println()
return
}
if devint, err = strconv.Atoi(args[2]); err != nil {
log.Error().Msg("invalid parameter, must be integer of adapter ID")
println()
return
}
deimos.Sniff(devint)
default:
log.Error().Msg("unknown sub-command: " + args[1])
println()
}
} else {
getHelp(getArgs(args))
}
case "scan":
if len(args) > 1 {
switch args[1] {
case "list":
fallthrough
case "status":
fmt.Println(getArgs(args) + ": start new default BLE scan")
case "start":
if len(args) > 2 {
switch args[2] {
case "tracking":
*projVars.GlobalConfig.CooleanFlags.Tracking = true
*projVars.GlobalConfig.CooleanFlags.Attack = false
case "attack":
*projVars.GlobalConfig.CooleanFlags.Tracking = false
*projVars.GlobalConfig.CooleanFlags.Attack = true
default:
// getHelp(getArgs(args))
log.Error().Msg("unknown sub command: " + args[2])
println()
}
}
bleScan()
}
} else {
getHelp(getArgs(args))
}
case "rpc":
if len(args) > 1 {
switch args[1] {
case "status":
fmt.Println(getArgs(args) + ": ")
case "server":
fmt.Println(getArgs(args) + ": ")
case "client":
fmt.Println(getArgs(args) + ": ")
}
} else {
getHelp(getArgs(args))
}
/*
case "wrath":
if len(args) < 2 {
getHelp("wrath")
return
}
wrath(args[1])
*/
case "clear":
print("\033[H\033[2J")
// termenv.ClearScreen()
default:
log.Error().Msg("unknown command: " + cmd)
println()
}
}
func getHelp(target string) {
fmt.Println("pRaNkeD! (help not available just yet.)")
fmt.Println()
return
/*
var lines []string
lines = append(lines, "help: "+target)
switch target {
case "meta":
var list string
for _, cmd := range cmds {
list = list + cmd + ", "
}
fmt.Println("Enabled commands: ")
list = strings.TrimSuffix(list, ", ")
fmt.Println(list)
fmt.Println()
default:
log.Error().Msg("Help entry not found!")
fmt.Println()
}
*/
}
func StartCLI() {
var hist []string
p := cli.New(
executor,
completer,
cli.OptionPrefix("ProtoMolecule"+"> "),
// cli.OptionPrefixBackgroundColor(cli.Black),
cli.OptionPrefixTextColor(cli.Cyan),
cli.OptionHistory(hist),
cli.OptionSuggestionBGColor(cli.Black),
cli.OptionSuggestionTextColor(cli.White),
cli.OptionSelectedSuggestionBGColor(cli.Black),
cli.OptionSelectedSuggestionTextColor(cli.Cyan),
// prompt.OptionLivePrefix
cli.OptionTitle("ProtoMolecule"),
)
p.Run()
}

44
go.mod

@ -1,37 +1,17 @@
module protomolecule
go 1.19
go 1.16
require (
git.tcp.direct/Mirrors/go-prompt v0.3.0
git.tcp.direct/kayos/prototooth v0.5.1
github.com/google/gopacket v1.1.19
github.com/json-iterator/go v1.1.12
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/prologic/bitcask v0.3.10
github.com/rs/zerolog v1.27.0
tinygo.org/x/bluetooth v0.5.0
)
require (
github.com/JuulLabs-OSS/cbgo v0.0.2 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mattn/go-tty v0.0.4 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muka/go-bluetooth v0.0.0-20220819143208-8b1989180f4c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/term v1.1.0 // indirect
github.com/plar/go-adaptive-radix-tree v1.0.4 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
github.com/charmbracelet/bubbles v0.7.9 // indirect
github.com/charmbracelet/bubbletea v0.13.2 // indirect
github.com/fatih/color v1.10.0
github.com/go-ole/go-ole v1.2.5 // indirect
github.com/godbus/dbus/v5 v5.0.4 // indirect
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
github.com/muka/go-bluetooth v0.0.0-20201211051136-07f31c601d33 // indirect
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0 // indirect
tinygo.org/x/bluetooth v0.3.0
)

431
go.sum

@ -1,437 +1,124 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.tcp.direct/Mirrors/go-prompt v0.3.0 h1:4IXTCPeuMfoemcJ+riSuKQhAx1rJo5hTtuSkr/IFq+A=
git.tcp.direct/Mirrors/go-prompt v0.3.0/go.mod h1:x8Gs5SGQbFzTglOjiK16geOcdw1dVZas3HYkfx0o6ls=
git.tcp.direct/kayos/prototooth v0.5.1 h1:j/WOekGSZLGCt1gqLFAedWEooprTyxYEIyQKei4kcew=
git.tcp.direct/kayos/prototooth v0.5.1/go.mod h1:sRAZ69v83VhnR037/DylbPPBwKb5UCoqEurDFNum0fM=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/JuulLabs-OSS/cbgo v0.0.2 h1:gCDyT0+EPuI8GOFyvAksFcVD2vF4CXBAVwT6uVnD9oo=
github.com/JuulLabs-OSS/cbgo v0.0.2/go.mod h1:L4YtGP+gnyD84w7+jN66ncspFRfOYB5aj9QSXaFHmBA=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgould/http v0.0.0-20190627042742-d268792bdee7/go.mod h1:BTqvVegvwifopl4KTEDth6Zezs9eR+lCWhvGKvkxJHE=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/charmbracelet/bubbles v0.7.9 h1:VaXFgxebUf7i+vhDCT72msF+5C+rj4uUkjik6/zgWLU=
github.com/charmbracelet/bubbles v0.7.9/go.mod h1:5WX1sSSjNCgCrzvRMN/z23HxvWaa+AI16Ch0KPZPeDs=
github.com/charmbracelet/bubbletea v0.13.1/go.mod h1:tp9tr9Dadh0PLhgiwchE5zZJXm5543JYjHG9oY+5qSg=
github.com/charmbracelet/bubbletea v0.13.2 h1:fSOx3q0/VbA3ChWeiNcUsNeNysD9FFWD1tZypShBuCQ=
github.com/charmbracelet/bubbletea v0.13.2/go.mod h1:okqaA5VF0aSpEZ2HB+L/cxVw2HthIDZ1dmWoRZs8/4g=
github.com/charmbracelet/lipgloss v0.1.2 h1:D+LUMg34W7n2pkuMrevKVxT7HXqnoRHm7IoomkX3/ZU=
github.com/charmbracelet/lipgloss v0.1.2/go.mod h1:5D8zradw52m7QmxRF6QgwbwJi9je84g8MkWiGN07uKg=
github.com/containerd/console v1.0.1 h1:u7SFAJyRqWcG6ogaMAx3KjSTy1e3hT9QxqX7Jco7dRc=
github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/frankban/quicktest v1.10.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY=
github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-tty v0.0.4 h1:NVikla9X8MN0SQAqCYzpGyXv0jY7MNl3HOWD2dkle7E=
github.com/mattn/go-tty v0.0.4/go.mod h1:u5GGXBtZU6RQoKV8gY5W6UhMudbR5vXnUe7j3pxse28=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muka/go-bluetooth v0.0.0-20210812063148-b6c83362e27d/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/muka/go-bluetooth v0.0.0-20220819143208-8b1989180f4c h1:CTU9zYG49RMMVTZwRcC78uBJ5gGmvLSgQXCDn1n4jJY=
github.com/muka/go-bluetooth v0.0.0-20220819143208-8b1989180f4c/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxmAOow=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68 h1:y1p/ycavWjGT9FnmSjdbWUlLGvcxrY0Rw3ATltrxOhk=
github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDCiKYPh8=
github.com/muesli/termenv v0.8.1 h1:9q230czSP3DHVpkaPDXGp0TOfAwyjyYwXlUCQxQSaBk=
github.com/muesli/termenv v0.8.1/go.mod h1:kzt/D/4a88RoheZmwfqorY3A+tnsSMA9HJC/fQSFKo0=
github.com/muka/go-bluetooth v0.0.0-20200619025933-f6113f7141c5/go.mod h1:yV39+EVOWdnoTe75NyKdo9iuyI3Slyh4t7eQvElUbWE=
github.com/muka/go-bluetooth v0.0.0-20201211051136-07f31c601d33 h1:p3srutpE8TpQmOUQ5Qw94jYFUdoG2jBbILeYLroQNoI=
github.com/muka/go-bluetooth v0.0.0-20201211051136-07f31c601d33/go.mod h1:dMCjicU6vRBk34dqOmIZm0aod6gUwZXOXzBROqGous0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/paypal/gatt v0.0.0-20151011220935-4ae819d591cf/go.mod h1:+AwQL2mK3Pd3S+TUwg0tYQjid0q1txyNUJuuSmz8Kdk=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/plar/go-adaptive-radix-tree v1.0.4 h1:Ucd8R6RH2E7RW8ZtDKrsWyOD3paG2qqJO0I20WQ8oWQ=
github.com/plar/go-adaptive-radix-tree v1.0.4/go.mod h1:Ot8d28EII3i7Lv4PSvBlF8ejiD/CtRYDuPsySJbSaK8=
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03/go.mod h1:Z9+Ul5bCbBKnbCvdOWbLqTHhJiYV414CURZJba6L8qA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prologic/bitcask v0.3.10 h1:HXygU8zCvW5gLpZ8aQECPk5iV/YQ3hcqdg/zVeES6s0=
github.com/prologic/bitcask v0.3.10/go.mod h1:8RKJdbHLE7HFGLYSGu9slnYXSV7DMIucwVkaIYOk9GY=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/suapapa/go_eddystone v1.3.1/go.mod h1:bXC11TfJOS+3g3q/Uzd7FKd5g62STQEfeEIhcKe4Qy8=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/btree v0.2.2/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/redcon v1.4.0/go.mod h1:IGzxyoKE3Ea5AWIXo/ZHP+hzY8sWXaMKr7KlFgcWSZU=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85 h1:jqhIzSw5SQNkbu5hOGpgMHhkfXxrbsLJdkIRcX19gCY=
golang.org/x/exp v0.0.0-20200228211341-fcea875c7e85/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee h1:4yd7jl+vXjalO5ztz6Vc1VADv+S/80LGJmyl1ROJ2AI=
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/sys v0.0.0-20200826173525-f9321e4c35a6/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0 h1:g9s1Ppvvun/fI+BptTMj909BBIcGrzQ32k9FNlcevOE=
golang.org/x/sys v0.0.0-20210414055047-fe65e336abe0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200925191224-5d1fdd8fa346/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.53.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
tinygo.org/x/bluetooth v0.5.0 h1:UftQTmx/snuTbeoS/R6+ZixmxSl5d6BvyfxlmD8eDng=
tinygo.org/x/bluetooth v0.5.0/go.mod h1:3rm7IKtmhP7aU2XRJI/Ods3J9Lqc3BAPPTNZmTtb42Q=
tinygo.org/x/drivers v0.14.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/bluetooth v0.3.0 h1:xy6f1aCAA5VzaXcx+qs4od6ZpiNu6pa/ufnZP7Db2ss=
tinygo.org/x/bluetooth v0.3.0/go.mod h1:zg3FxlQIY6vtbtpK5dU8h5BzQiLoE3hGthXTR0GNEn0=
tinygo.org/x/drivers v0.15.1/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/drivers v0.16.0/go.mod h1:uT2svMq3EpBZpKkGO+NQHjxjGf1f42ra4OnMMwQL2aI=
tinygo.org/x/drivers v0.20.0/go.mod h1:uJD/l1qWzxzLx+vcxaW0eY464N5RAgFi1zTVzASFdqI=
tinygo.org/x/tinyfont v0.2.1/go.mod h1:eLqnYSrFRjt5STxWaMeOWJTzrKhXqpWw7nU3bPfKOAM=
tinygo.org/x/tinyfs v0.1.0/go.mod h1:ysc8Y92iHfhTXeyEM9+c7zviUQ4fN9UCFgSOFfMWv20=
tinygo.org/x/tinyterm v0.1.0/go.mod h1:/DDhNnGwNF2/tNgHywvyZuCGnbH3ov49Z/6e8LPLRR4=

171
main.go

@ -1,161 +1,32 @@
package main
import (
"flag"
"fmt"
tea "github.com/charmbracelet/bubbletea"
"os"
"os/signal"
"syscall"
"time"
projVars "protomolecule/src/config"
"protomolecule/src/dust"
"protomolecule/src/eros"
"protomolecule/src/protogen"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"protomolecule/src/decls"
"protomolecule/src/scanStuff"
"tinygo.org/x/bluetooth"
)
var (
manufLoad bool
GitVersion string
BuildDate string
)
var initialModel = decls.Model{
// Our to-do list is just a grocery list
Choices: []bluetooth.ScanResult{},
func loginit() {
// setup json logfile
// TODO: make this a command line argument
var logDir string = "./.logs/"
err := os.MkdirAll(logDir, 0755)
if err != nil {
panic(err.Error())
}
Now := time.Now()
date := Now.Format(time.RFC3339)
logFileName := date + ".log"
lf, err := os.OpenFile(logDir+logFileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
panic(err.Error())
}
// define pretty printer
consoleWriter := zerolog.ConsoleWriter{Out: os.Stderr}
// initialize simultaneous pretty printing and json logging
multi := zerolog.MultiLevelWriter(consoleWriter, lf)
log.Logger = zerolog.New(multi).With().Timestamp().Logger()
// suppress debug messages unless -d is called
zerolog.SetGlobalLevel(zerolog.InfoLevel)
}
func cliFlags() {
flag.Parse()
if *projVars.GlobalConfig.CooleanFlags.Debug {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
manufLoad = *projVars.GlobalConfig.StringFlags.MACVendorFile != ""
}
func sigHandler() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
for {
select {
case <-c:
log.Warn().
Msg("Interrupt detected, stopping scan...")
for _, scan := range protogen.ScanMgr.Scans {
for _, dev := range scan.Device {
if dev.Connected {
_ = dev.Conn.Disconnect()
}
}
_ = scan.Stop()
}
}
}
}
func init() {
// Initializing an instance of our scan manager to keep track of concurrent scans
// NOTE: Devices are kept track of per Scan via a nested map of eros.Device instances
//
// TODO: Get all of this management stuff into its own package (structs and maps and initialization of the former)
// TODO: Name these structs and maps and instances more uniformly and idiomatically
dust.PrintBanner()
protogen.ScanMgr = &protogen.Meta{
Scans: make(map[int]*protogen.Scan),
Mailbox: make(chan protogen.Postcard),
}
loginit()
cliFlags()
go sigHandler()
if len(GitVersion) > 0 {
log.Info().
Str("Version", GitVersion).
Str("Built", BuildDate).
Msg("Initializing protomolecule")
}
// see ./src/eros
log.Debug().Msg("Initializing database engine")
eros.Awaken()
// loading manufacturer info from json file determined by flag
if manufLoad {
eros.ManufLoad()
}
}
func earToTheWire() {
var sublog zerolog.Logger
// Define our IPC callbacks here
interpret := func(m protogen.Postcard) {
switch m.Command {
case "quit":
sublog.Warn().
Int("from", m.From).
Msg("IPC_QUIT")
os.Exit(0)
}
}
for {
select {
case postcard := <-protogen.ScanMgr.Mailbox:
sublog = log.With().Int("scan_id", postcard.From).Logger()
interpret(postcard)
}
}
}
func bleScan() {
var scan *protogen.Scan
// Note: the tinygo bluetooth (prototooth here) library does already have a channel system for starting and stopping scans
// the rational behind implementing an additional IPC system has to do with potentially doing more than just BLE
//
// this starts our IPC listener so that we are ready to catch any incoming IPC messages before we start any scans
go earToTheWire()
scan = protogen.ScanMgr.NewScan()
// time.Sleep(30 * time.Millisecond)
go dust.Must("Scan", scan.Start())
// A map which indicates which choices are selected. We're using
// the map like a mathematical set. The keys refer to the indexes
// of the `choices` slice, above.
Selected: make(map[int]struct{}),
}
func main() {
defer eros.Slumber()
StartCLI()
p := tea.NewProgram(initialModel)
go scanStuff.Scanners(initialModel, p)
if err := p.Start(); err != nil {
fmt.Printf("Alas, there's been an error: %v", err)
os.Exit(1)
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

@ -1,54 +0,0 @@
// arboghast is a package focused on fuzzing BLE devices
package arboghast
import (
"sync"
bluetooth "git.tcp.direct/kayos/prototooth"
projVars "protomolecule/src/config"
)
type Circus struct {
Trick map[int]*Fuzzy
uuid_pool []bluetooth.UUID
mu *sync.Mutex
}
type Fuzzy struct {
config *bluetooth.AdvertisementOptions
ad *bluetooth.Advertisement
mu *sync.Mutex
}
var circus *Circus
func Awaken() {
circus = &Circus{
Trick: make(map[int]*Fuzzy),
uuid_pool: []bluetooth.UUID{},
mu: &sync.Mutex{},
}
}
func NewEntropicFuzzer() *Fuzzy {
options := &bluetooth.AdvertisementOptions{
LocalName: RandomAlpha(16),
// Interval:
}
mutex := &sync.Mutex{}
ad := projVars.GlobalConfig.GetScanAdapter().DefaultAdvertisement()
ad.Configure(*options)
return &Fuzzy{
config: options,
ad: ad,
mu: mutex,
}
}
func (f *Fuzzy) StartAd() {
f.ad.Start()
}

@ -1,34 +0,0 @@
package arboghast
import (
"math/rand"
"time"
"unsafe"
)
var src = rand.NewSource(time.Now().UnixNano())
const (
letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
func RandomAlpha(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = src.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return *(*string)(unsafe.Pointer(&b))
}

@ -1,16 +0,0 @@
package blueStuff
import "git.tcp.direct/kayos/prototooth"
// controls connections to found devices to get service characteristics
// TODO: disable the scan adapter, enable the attack adapter (this step pmight not be needed)
// use the result.LocalName passed from scanStuff to start connection
// once connected, for list of service characteristics and check for read / write for each
// loog all of this to eros
func pathways(target bluetooth.ScanResult) {
println("Discovery")
// device, err := projVars.GlobalConfig.GetScanAdapter().Connect(bluetooth.ScanResult.Address, bluetooth.ConnectionParams{})
// srvcs, err := projVars.GlobalConfig.GetScanAdapter().
}

@ -1,99 +0,0 @@
package common
import (
"flag"
bluetooth "git.tcp.direct/kayos/prototooth"
)
type Mode uint32
const (
ModeNone Mode = iota
ModeScan
ModeTrack
ModeAttack
)
func (m Mode) String() string {
return [...]string{"None", "Track", "Attack"}[m]
}
// Tooth is a bluetooth adapter.
type Tooth struct {
ID int
Usecase Mode
*bluetooth.Adapter
}
type Config struct {
// ManuFile is a place holder for a file with list of all known Manufacturers
// this is actually stored by eros and likely deprecated.
ManuFile string
Teeth []*Tooth
CooleanFlags struct {
Debug *bool
Attack *bool
Tracking *bool
}
StringFlags struct {
MACVendorFile *string
MACTarget *string
}
}
func (c *Config) GetScanAdapter() *bluetooth.Adapter {
if len(c.Teeth) == 1 {
return c.Teeth[0].Adapter
}
var choice *bluetooth.Adapter
for _, v := range c.Teeth {
if v.Usecase == ModeScan || v.Usecase == ModeTrack {
choice = v.Adapter
break
}
}
return choice
}
var DefaultConfig = Config{
ManuFile: DefManuFile,
Teeth: []*Tooth{
&Tooth{
ID: 0,
Usecase: ModeNone,
Adapter: bluetooth.DefaultAdapter,
},
},
CooleanFlags: struct {
Debug *bool
Attack *bool
Tracking *bool
}{
Debug: flag.Bool("d", false, "global debug logging and pretty printing"),
Attack: flag.Bool("a", false, "attack mode"),
Tracking: flag.Bool("t", false, "device tracking mode"),
},
StringFlags: struct {
MACVendorFile *string
MACTarget *string
}{
MACVendorFile: flag.String("m", DefManuFile, "json file with manufacturer identifying data to preload into eros"),
MACTarget: flag.String("w", "00:00:00:00:00:00", "MAC address of target"),
},
}
const DefManuFile = "./ManufUUID.json"
// Service UUIDs for testing
var (
SonosScanTest = bluetooth.New16BitUUID(0xfe07)
AppleScanTest = bluetooth.New16BitUUID(0x004c)
GlobalConfig *Config
)
func init() {
GlobalConfig = &DefaultConfig
}

@ -1,34 +0,0 @@
package cortazar
//Intended to be used as the target analysis package
//for example
/*
1. attempt bonding (LTK exchange) with target, while in aggressive attack mode
2. store current LTK associated with target to device in eros
3. disconnect and forget target
4. recconect to target, pair, bond, store LTK, disconnect, forget -- repeatTK
5. compare LTKs for entropy
** could also be used for link key which is the pin established during pairing
*/
//---------
/*
device metrics
- location and device type/manufacturer/etc...
- when and where a device is seen over time
*/
//-----------
/*
*/

96
src/decls/tui.go Normal file

@ -0,0 +1,96 @@
package decls
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"tinygo.org/x/bluetooth"
)
type Model struct {
Choices []bluetooth.ScanResult // device list
cursor int // which device to place the cursor upon
Selected map[int]struct{} // devices in the list which are Selected
}
func (m Model) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
return nil
}
/*
func tick() tea.Cmd {
return tea.Tick(time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}*/
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
// Is it a key press?
case tea.KeyMsg:
// Cool, what was the actual key pressed?
switch msg.String() {
// These keys should exit the program.
case "ctrl+c", "q":
return m, tea.Quit
// The "up" and "k" keys move the cursor up
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
// The "down" and "j" keys move the cursor down
case "down", "j":
if m.cursor < len(m.Choices) - 1 {
m.cursor++
}
// The "enter" key and the spacebar (a literal space) toggle
// the selected state for the item that the cursor is pointing at.
case "enter", " ":
_, ok := m.Selected[m.cursor]
if ok {
delete(m.Selected, m.cursor)
} else {
m.Selected[m.cursor] = struct{}{}
}
}
}
// Return the updated pm_tea_model to the Bubble Tea runtime for processing.
// Note that we're not returning a command.
return m, nil
}
func (m Model) View() string {
// The header
s := "Recent Devices\n\n"
// Iterate over our choices
for i, choice := range m.Choices {
// Is the cursor pointing at this choice?
cursor := " " // no cursor
if m.cursor == i {
cursor = ">" // cursor!
}
// Is this choice selected?
checked := " " // not selected
if _, ok := m.Selected[i]; ok {
checked = "x" // selected!
}
// Render the row
s += fmt.Sprintf("%s [%s] %s %d %s\n",
cursor,
checked,
choice.Address.String(),
choice.RSSI,
choice.LocalName())
}
// The footer
s += "\nPress q to quit.\n"
// Send the UI for rendering
return s
}

@ -1,77 +0,0 @@
package deimos
import (
"errors"
"fmt"
"github.com/google/gopacket/dumpcommand"
"github.com/google/gopacket/pcap"
"github.com/rs/zerolog/log"
"strconv"
"strings"
)
type DevMap map[int]pcap.Interface
var Devices DevMap
var Interface *pcap.Handle
func init() {
Devices = make(map[int]pcap.Interface)
}
func getInterfaces() []pcap.Interface {
devices, err := pcap.FindAllDevs()
if err != nil {
log.Error().Err(err).Msg("failed to get network interfaces")
return nil
}
return devices
}
func getBlueDevices() []pcap.Interface {
var blues []pcap.Interface
devices := getInterfaces()
for n, device := range devices {
if strings.Contains(strings.ToLower(device.Name), "bluetooth") {
Devices[n] = device
blues = append(blues, device)
}
if strings.Contains(strings.ToLower(device.Name), "hci") {
Devices[n] = device
blues = append(blues, device)
}
}
return blues
}
func PrintBlueDevices() {
getBlueDevices()
for n := range Devices {
devid := strconv.Itoa(n)
fmt.Println("[" + devid + "] " + Devices[n].Name)
}
}
func Sniff(dev int) error {
var (
err error
d pcap.Interface
preInterface *pcap.InactiveHandle
ok bool
)
if d, ok = Devices[dev]; !ok {
return errors.New("adapter ID does not exist")
}
if preInterface, err = pcap.NewInactiveHandle(d.Name); err != nil {
return err
}
defer preInterface.CleanUp()
if err = preInterface.SetPromisc(true); err != nil {
return err
}
if Interface, err = preInterface.Activate(); err != nil {
return err
}
go dumpcommand.Run(Interface)
return nil
}

File diff suppressed because one or more lines are too long

@ -1,71 +1,20 @@
package dust
import (
"encoding/base64"
//pt "github.com/pterm/pterm"
"github.com/rs/zerolog/log"
//projVars "protomolecule/src/vars"
projVars "protomolecule/src/vars"
)
func Must(action string, err error) {
if err != nil {
switch err.Error() {
case "AdapterExists: Launch helper exited with unknown return code 1":
log.Error().Msg("Is your bluetooth adapter enabled? Linux: is bluetoothd started?")
default:
log.Fatal().Err(err).Str("action", action).Msg("FATAL_ERROR")
}
panic("failed to" + action + ": " + err.Error())
}
}
/* DID: Name devices that do not have a Local Device name
We solve this with our scan manager by assigning them an ID as a key in our map(s)
See: eros.Device & scanStuff.Meta & scanStuff.Scan
TODO: we should consider making a package to contain the concurrency managment mechanisms
func FirstList() {
projVars.ScanList = make(map[string]string)
}
//TODO: Name devices that do not have a Local Device name
func OhNameMeZaddy() {
}
*/
func b64d(str string) string {
data, err := base64.StdEncoding.DecodeString(str)
if err != nil {
return err.Error()
}
return string(data)
}
/*
func Splash() {
const banner string = "IOKWhOKWhOKWhMK34paE4paE4paEICAgICAgICDiloTiloTiloTiloTiloQgICAgICDigKIg4paMIOKWhCDCty4gICAgICAg4paE4paE4paMICDiloTiloTiloQgLiDiloTiloTCtyDiloTigKIg4paE4paM4paE4paE4paMICDiloTiloTiloQgLgrilpDilogg4paE4paI4paA4paEIOKWiMK34paqICAgICDigKLilojiloggIOKWqiAgICAgwrfilojilogg4paQ4paI4paI4paI4paq4paqICAgICDilojilojigKIgIOKWgOKWhC7iloDCt+KWkOKWiCDilozilqrilojilqrilojilojilozilojilojigKIgIOKWgOKWhC7iloDCtwog4paI4paI4paAwrfilpDiloDiloDiloQgIOKWhOKWiOKWgOKWhCAg4paQ4paILuKWqiDiloTilojiloDiloQg4paQ4paIIOKWjOKWkOKWjOKWkOKWiMK3IOKWhOKWiOKWgOKWhCDilojilojilqogIOKWkOKWgOKWgOKWquKWhOKWiOKWiCDiloTiloTilojilozilpDilojilozilojilojilqogIOKWkOKWgOKWgOKWquKWhArilpDilojilqrCt+KAouKWkOKWiOKAouKWiOKWjOKWkOKWiOKWjC7ilpDilowg4paQ4paI4paMwrfilpDilojilowu4paQ4paM4paI4paIIOKWiOKWiOKWjOKWkOKWiOKWjOKWkOKWiOKWjC7ilpDilozilpDilojilozilpDilozilpDilojiloTiloTilozilpDilojilojilojilozilpDilojiloTilojilozilpDilojilozilpDilozilpDilojiloTiloTilowKLuKWgCAgIC7iloAgIOKWgCDiloDilojiloTiloDilqog4paA4paA4paAICDiloDilojiloTiloDilqriloDiloAgIOKWiOKWquKWgOKWgOKWgCDiloDilojiloTiloDilqou4paA4paA4paAICDiloDiloDiloAgwrfiloDiloDiloAgIOKWgOKWgOKWgCAu4paA4paA4paAICDiloDiloDiloAgCg=="
lines := make(map[int]string)
var count int = 0
s := b64d(banner)
xerox := bufio.NewScanner(strings.NewReader(s))
from := pt.NewRGB(180, 111, 229)
to := pt.NewRGB(0, 162, 173)
for xerox.Scan() {
lines[count] = xerox.Text()
count++
//from.Fade(0
}
total := count
count = 0
for _, line := range lines {
from.Fade(0, float32(total), float32(count), to).Println(line[count])
count++
}
}
*/

@ -1,388 +1,6 @@
// Eros controls the Bitcask db that holds the devices captured during scanning
//
// Addr (Key)
// Device Local Name (LocalName)
// Raw Advertisement Packet (Advertisement)
// Services UUIDs (Services)
// TODO:
//
// Output to report
package eros
import (
"bufio"
"os"
"strconv"
"strings"
"sync"
"time"
jsoniter "github.com/json-iterator/go"
projVars "protomolecule/src/config"
bluetooth "git.tcp.direct/kayos/prototooth"
"github.com/prologic/bitcask"
"github.com/rs/zerolog/log"
)
var (
json = jsoniter.ConfigCompatibleWithStandardLibrary
dbs = []string{
"devices", // details about devices discovered
"exploits", // details about exploits to be used against BLE devices
"services", // definitions of various bluetooth services and will ultimately be updated via an HTTP repository
"manufacturers", // manufacturer to UUID correlations and info
}
err error
Manufacturers ManufData
// Exploits Exploit
// DataDir - should be defined by config or cmd flag
DataDir string = "./.eros-data/"
)
var DB map[string]*bitcask.Bitcask
// Ingest UUID will add the UUID to the manufacturer if it doesn't already exist
func (manuf *Manufacturer) IngestUUID(uuid bluetooth.UUID) bool {
contains := func(s []bluetooth.UUID, v bluetooth.UUID) bool {
for _, a := range s {
if a == v {
return true
}
}
return false
}
if contains(manuf.UUIDs, uuid) {
return false
}
manuf.UUIDs = append(manuf.UUIDs, uuid)
return true
}
func FinalizeDevice(bigidea Device) *Device {
bigidea.mu = &sync.RWMutex{}
return &bigidea
}
func (d *Device) ConnectHandler(target bluetooth.Addresser, c bool) {
d.mu.Lock()
defer d.mu.Unlock()
if c {
d.Connected = true
} else {
d.Connected = false
}
}
// ManufLoad loads data from a json file containing UUID manufacturer associations
func ManufLoad() {
path := *projVars.GlobalConfig.StringFlags.MACVendorFile
var uuid bluetooth.UUID
log.Info().
Str("file", path).
Msg("Loading manufacturer data...")
f, err := os.Open(path)
if err != nil {
log.Error().Err(err).
Msg("Failed to open JSON file")
}
defer f.Close()
scan := bufio.NewScanner(f)
for scan.Scan() {
var mf *Manufacturer
var newManuf bool = true
bytes := []byte(strings.TrimSpace(scan.Text()))
json.Unmarshal(bytes, &mf)
utmp, err := strconv.ParseUint(mf.UString, 0, 16)
if err != nil {
log.Fatal().Str("file", path).
Str("string", mf.UString).
Err(err).Msg("MANUFACTURER_PARSE_ERROR")
}
uuid = bluetooth.New16BitUUID(uint16(utmp))
mf.IngestUUID(uuid)
log.Debug().
Str("Manufacturer", mf.Name).
Str("UUID_String", mf.UString).
// Interface("UUID_Type", uuid).
// Str("Website", mf.Website).
Msg("LOADED_MANUFACTURER_DATA")
mf.UString = ""
if len(Manufacturers.Entries) == 0 {
log.Debug().Msg("new manufacturer")
Manufacturers.Entries = append(Manufacturers.Entries, *mf)
continue
}
for i, m := range Manufacturers.Entries {
if m.Name != mf.Name {
continue
}
newManuf = false
mf = &Manufacturers.Entries[i]
mf.IngestUUID(uuid)
log.Debug().Str("Manufacturer", mf.Name).
Int("UUID_Count", len(mf.UUIDs)).
// Interface("UUIDs", mf.UUIDs).
Msg("EXISTING_MANUFACTURER_FOUND")
}
if newManuf {
Manufacturers.Entries = append(Manufacturers.Entries, *mf)
}
}
for _, manuf := range Manufacturers.Entries {
var jsonData []byte
jsonData, err = json.Marshal(manuf)
if err != nil {
log.Fatal().Err(err).
Msg("EROS_FATAL_MANUFACTURER_JSON_MARSHAL")
}
err := DB["manufacturers"].Put([]byte(manuf.Name), jsonData)
if err != nil {
log.Fatal().Err(err).
Msg("EROS_FATAL_MANUFACTURER_JSON_STORE")
}
log.Debug().Str("Name", manuf.Name).
Msg("EROS_MANUFACTURER_STORE")
}
}
// Awaken - create the data directory if it does not exist; initialize bitcask in this directory
func Awaken() {
DB = make(map[string]*bitcask.Bitcask)
for _, name := range dbs {
DB[name], err = bitcask.Open(DataDir+name, bitcask.WithMaxValueSize(1024*1024*1024))
if err != nil {
panic(err.Error())
}
}
}
func logErr(err error) {
if err != nil {
log.Error().Err(err).
Msg("EROS_DATABASE_ERROR")
}
}
// Slumber - Clean up database entries, sync them to persistent storage, and safelty close the database.
func Slumber() {
for _, db := range DB {
logErr(db.Merge())
logErr(db.Sync())
logErr(db.Close())
}
}
// Exists - check if a device is present in the Database
func Exists(Addr string) bool {
if DB["devices"].Has([]byte(Addr)) {
return true
}
return false
}
// Remember stores device details into the database via an argument
func Remember(dev *Device) error {
var err error
var rhist map[time.Time]int16
if !Exists(dev.Addr) {
dev.Discovered = time.Now()
rhist = make(map[time.Time]int16)
} else {
re, _ := Recall(dev.Addr)
dev.Discovered = re.Discovered
rhist = re.RSSIhist
}
rhist[time.Now()] = dev.RSSIlast
dev.RSSIhist = rhist
dev.Seen = time.Now()
var jsonData []byte
jsonData, err = json.Marshal(dev)
if err != nil {
return err
}
err = DB["devices"].Put([]byte(dev.Addr), jsonData)
return err
}
// Recall - retrieve device details from the database
func Recall(Addr string) (Device, error) {
var err error
var bytes []byte
var member Device
bytes, err = DB["devices"].Get([]byte(Addr))
if err != nil {
member = Device{}
return member, err
}
if err := json.Unmarshal(bytes, &member); err != nil {
log.Panic().Err(err).Msg("Failed to unmarshal device")
}
return member, err
}
func Backup(path string) error {
for _, db := range DB {
logErr(db.Merge())
logErr(db.Sync())
err := db.Backup(path)
if err != nil {
return err
}
}
return nil
}
/*
// Hypnosis - retrieve new exploits/attacks from a remote http repository
func Hypnosis(repo string) {
e:= echo.New()
// CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE} //TODO: possibly trim the methods
}))
// GET
e.GET("/exploits", func(c echo.Context) error { // This will definitely need to be updated
// Build request
req, err := http.NewRequest("GET", repo, nil)
if err != nil {
fmt.Println("Error in GET request: ", err)
}
// Certificate sanity checks
caCert, err := os.Readfile("server.crt")
if err != nil {
log.Fatal(err)
}
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
log.Fatal(err)
}
// Add certificates
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// Create client
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
Certificates: []tls.Certificate{cert},
},
},
}
// Send request
res, err := client.Do(req)
if err != nil {
fmt.Println("Client Error: ", err)
}
// Defer body close
defer res.Body.Close()
// Late binding data from JSON
var exp Exploit
// Decode JSON stream
If err := json.NewDecoder(res.Body).Decode(&exp); err != nil {
fmt.Println(err)
}
return c.JSON(http.StatusOK, exp)
})
}
// Trauma - store details of an exploit/attack against BLE devices
func Trauma(exp *Exploit) error {
var err error
var rhist map[time.Time]int16
if !Known(exp.Addr) {
exp.Discovered = time.Now()
rhist = make(map[time.Time]int16)
} else {
re, _ := Flashback(exp.Addr)
exp.Discovered = re.Discovered
rhist = re.RSSIhist
}
rhist[time.Now()] = exp.RSSIlast
exp.RSSIhist = rhist
exp.Seen = time.Now()
var jsonData []byte
jsonData, err = json.Marshal(exp)
if err != nil {
return err
}
err = DB["exploits"].Put([]byte(exp.Addr), jsonData)
return err
}
func FinalizeExploit(bigidea Exploit) *Exploit {
bigidea.mu = &sync.RWMutex{}
return &bigidea
}
*/
// Known - check if an exploit is present in the database
func Known(Addr string) bool {
return DB["exploits"].Has([]byte(Addr))
}
// Flashback - retrieve details for the named exploit/attack
func Flashback(Addr string) (Exploit, error) {
var err error
var bytes []byte
var member Exploit
bytes, err = DB["exploits"].Get([]byte(Addr))
if err != nil {
member = Exploit{}
return member, err
}
if err := json.Unmarshal(bytes, &member); err != nil {
log.Panic().Err(err).Msg("Failed to unmarshal exploit")
}
return member, err
}
//
// Bitcask DB holds the devices captured during scanning
//
//

@ -1,88 +0,0 @@
package eros
import (
bluetooth "git.tcp.direct/kayos/prototooth"
"sync"
"time"
)
type Permissions struct {
Read string
Write string
Notify string
}
type Characteristic struct {
Name string
UUID string
Permissions []Permissions
}
// Service - BLE service details to be marshalled into json before stored in bitcask
type Service struct {
Name string
UUID string
Category string
Characteristic []Characteristic
}
type ManufData struct {
Entries []Manufacturer
}
// Manufacturer holds some information about a device manufacturer
type Manufacturer struct {
Name string `json:"ManufString"`
Website string `json:"ManuFSite"`
// UUIDs is a slice of UUIDs that we associate with the parent Manufacturer
UUIDs []bluetooth.UUID
// this exists purely for json unmarshalling purposes
UString string `json:"ManufUUID"`
}
// Exploit - BLE service exploit details to be marshalled into json before stored in bitcask
type Exploit struct {
Name string
Target string // Should prabably be a struct of some sort since exploits target service chars
Category string
Vector Vector
Payload Payload
}
// Vector - TBD details of how an exploit initially attacks a BLE service
type Vector struct {
// placeholder
}
// Payload - TBD details of what an exploit will do to the device after the initial attack
type Payload struct {
// placeholder
}
// Device will hold details about the discoverd device
type Device struct {
Name string // local name of the device
Addr string // Broadcast MAC
Manufacturer string // Manufacturer Data if broadcast
RSSIlast int16 // Most Current RSSI
RSSIhist map[time.Time]int16 // Last RSSI readings
Alias string // Alias given to device
Trusted bool // Is is in the trusted devices
WakeAllowed bool // Does the device allow wake
// Services - see Service struct
Services []Service
// Discovered - timestamp from when the device was first discovered
Discovered time.Time
// Seen - timestamp from when the device was last seen
Seen time.Time
Connected bool
Conn *bluetooth.Device
// The most at-risk type we have so far for concurrency, needs to be mutexed
mu *sync.RWMutex
}

@ -1,305 +0,0 @@
package protogen
import (
"sync"
"time"
projVars "protomolecule/src/config"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"protomolecule/src/eros"
bluetooth "git.tcp.direct/kayos/prototooth"
)
// ScanMgr will keep track of concurrently running scans
var ScanMgr *Meta
// NewScan - here we are creating an "anonymous" instance of a Scan struct
// You'll notice it doesn't contain most of the data that actually is supposed to live within that struct
//
// This works because the integer ID is being used as a key that will have a global scope
//
// See: Remember function as it is defined in eros/eros.go
// Remember usage in this file (scanStuff/scanStuff.go
//
// In these examples we are sending an instance of a struct with some data to eros; allowing it to finish filling out the rest
// This can be done with just structs, but becomes problematic when trying to refer to them later
//
// ref: https://gobyexample.com/mutexes
// ref: https://golang.org/pkg/sync/#Mutex
// ref: https://www.geeksforgeeks.org/mutex-in-golang-with-examples/
func (m *Meta) NewScan() *Scan {
newid := len(m.Scans)
m.Scans[newid] = &Scan{
ID: newid,
Started: time.Now(),
}
scan := m.Scans[newid]
scan.Device = make(map[int]*eros.Device)
scan.mu = &sync.RWMutex{}
return scan
}
func (s *Scan) NewDevice(name string, addr string, manuf string, rssi int16) *eros.Device {
s.mu.Lock()
defer s.mu.Unlock()
draft := eros.Device{
Name: name,
Addr: addr,
Manufacturer: manuf,
RSSIlast: rssi,
}
return eros.FinalizeDevice(draft)
}
/*
func ManfCheck(TargetAdvertData bluetooth.AdvertisementPayload) string {
uuids := TargetAdvertData.ServiceUUIDOut()
var ManufacturerOut string
for _, i := uuids, 0; i < len(uuids); i++ {
//very closse to getting this
for lines, i := manuF.ManufS, 0; i < len(lines); i++ {
curUUID := lines[i].UUID
curUUID16Bit := bluetooth.New16BitUUID(curUUID.Get16Bit())
curMan := lines[i].WhosIts
curCheck := TargetAdvertData.HasServiceUUID(curUUID16Bit)
if curCheck == true {
println(curMan)
ManufacturerOut = curMan
} else {
ManufacturerOut = "No Manufacturer Data in UUID"
}
}
}
return ManufacturerOut
}
*/
// resultHandler is called by the bluetooth library upon device discovery to handle the result
func (s *Scan) resultHandler(scanAdapter *bluetooth.Adapter, result bluetooth.ScanResult) {
var sublog zerolog.Logger
payload := result.AdvertisementPayload
addr := result.Address.String()
lname := result.LocalName()
// adbytes := payload.Bytes()
rssi := result.RSSI
EnumedManuf := ""
// TODO: probably make this a function or a method in eros(?)
//
// ---attempts to set the manufacturer value----
// range through the uuids in the payload and compare
// known manufacturers list
uuids := payload.ServiceUUIDOut()
for range uuids {
// for every element within the manufacturers list
// check each UUID
for _, man := range eros.Manufacturers.Entries {
// pull the UUID the current element of the manufacturer list
currManufErosOut := man.UUIDs /* current element from manufacturer data */
// for each manufacturer's UUID: compare the advertisment payload for a match
for _, manusss := range currManufErosOut {
manufBool := payload.HasServiceUUID(manusss)
// if there is a UUID match then assign the Manufacturer's name
// to the output variable EnumedManuf
if manufBool == true {
EnumedManuf = man.Name
}
}
}
}
// ManufOut := ManfCheck(payload)
layToRest := func(dev *eros.Device) {
sublog.Debug().Msg("Storing data with Eros")
if err := eros.Remember(dev); err != nil {
log.Warn().Err(err).Msg("EROS_REMEMBER_FAILURE")
}
// simple eros test
fromEros, err := eros.Recall(addr)
if err != nil {
log.Fatal().Err(err).Msg("EROS_RECALL_FAILURE")
}
log.Info().
Str("Name", fromEros.Name).
Str("Addr", fromEros.Addr).
Str("ManuF", fromEros.Manufacturer). // needs to be changed back to fromEros.Manufacturer
Int16("Current_RSSI", fromEros.RSSIlast).
Int("Service_Count", len(fromEros.Services)).
Msg("EROS_RECALL")
}
// Service enumeration and storage
// TODO:
// Fix Logging output here -- Prob shouldnt output this much info
// for each service discovered
// only each device
sublog = log.With().Int("Scan_ID", s.ID).
Str("LocalName", lname).Str("Addr", addr).
Str("ManuF", EnumedManuf).
Int16("RSSI", rssi).Logger()
// Skipping duplicate results unless tracking mode enabled (to store RSSI as devices move)
if !*projVars.GlobalConfig.CooleanFlags.Tracking {
for _, dev := range s.Device {
if addr == dev.Addr {
return
}
}
}
// Upon finding new and valid info we update the time for last activity
s.Activity = time.Now()
dev := s.NewDevice(lname, addr, EnumedManuf, rssi)
projVars.GlobalConfig.GetScanAdapter().SetConnectHandler(dev.ConnectHandler)
// Record all the services advertised, append them into the nested struct within Device
for _, uuid := range uuids {
svc := &eros.Service{
UUID: uuid.String(),
}
dev.Services = append(dev.Services, *svc)
sublog.Info().Str("UUID", svc.UUID).Msg("SERVICE_DISCOVERED")
}
if !*projVars.GlobalConfig.CooleanFlags.Attack {
layToRest(dev)
return
}
sublog.Info().Str("Adapter", "Attempting Connection").Msg("ADAPTER_STATUS")
var connErr error
var err error
// TODO: make timeout values and intervals command line arguments instead of hard coding them
timeout := bluetooth.NewDuration(time.Duration(3) * time.Second)
interval := bluetooth.NewDuration(time.Duration(20) * time.Millisecond)
dev.Conn, connErr = projVars.GlobalConfig.GetScanAdapter().
Connect(result.Address, bluetooth.ConnectionParams{ConnectionTimeout: timeout, MinInterval: interval})
if connErr != nil {
sublog.Error().Err(connErr).Msg("CONNECT_ERROR")
layToRest(dev)
return
}
var targetServices []bluetooth.DeviceService
sublog.Info().
Str("status", "Connected, attempting to Read Target Services").
Msg("ADAPTER_STATUS")
if !dev.Connected {
layToRest(dev)
return
}
// ServBuf := make([]byte, 255)
Charbuf := make([]byte, 255)
targetServices, err = dev.Conn.DiscoverServices(nil)
if err != nil {
sublog.Error().Err(err).Msg("DISCOVER_SERVICE_ERROR")
}
// for SerReadPos, srvcs := range targetServices {
for _, srvcs := range targetServices {
charSer := eros.Service{
UUID: srvcs.String(),
}
sublog.Info().Str("Service UUID", charSer.UUID).
// Int("Bytes", SerReadPos).
// Str("Value", string(ServBuf[:SerReadPos])).
Msg("GATT_SERVICE")
sublog.Debug().Str("status", "Attempting to Retrieve Characteristic List").Msg("ADAPTER_STATUS")
chars, _ := srvcs.DiscoverCharacteristics(nil)
dev.Services = append(dev.Services, charSer)
for _, char := range chars {
ReadPos, _ := char.Read(Charbuf)
// flags := char.Flags
sublog.Info().Str("UUID", char.UUID().String()).
// Int("Bytes", ReadPos).
Str("Value", string(Charbuf[:ReadPos])).
// Bool("Read", flags.Read()).
// Bool("Write", flags.Write()).
Msg("SERVICE_CHARACTERISTIC")
}
}
// finished with this device
_ = dev.Conn.Disconnect()
dev.Connected = false
sublog.Info().Str("Adapter", "Successfully Disconnected From Target").Msg("ADAPTER_STATUS")
layToRest(dev)
// TODO: stop scan and call bluestuff func and pass it the local name value
}
func (s *Scan) Stop() error {
// TODO: fix this adapterId situation
err := projVars.GlobalConfig.GetScanAdapter().StopScan()
if err != nil {
return err
}
return nil
}
func (s *Scan) Start() error {
s.Started = time.Now()
// create list of devices in the Area of Operation
// log.Debug().Msg("Creating Device Map")
// Enable BLE interface
log.Debug().Msg("Enabling Adapter")
err := projVars.GlobalConfig.GetScanAdapter().Enable()
if err != nil {
return err
}
adapterId := projVars.GlobalConfig.GetScanAdapter().ID
log.Info().
Str("ID", adapterId).
Bool("MODE_TRACK", *projVars.GlobalConfig.CooleanFlags.Tracking).
Bool("MODE_ATTACK", *projVars.GlobalConfig.CooleanFlags.Attack).
Msg("Starting new ProtoMolecule BLE Scan")
err = projVars.GlobalConfig.GetScanAdapter().Scan(s.resultHandler)
if err != nil {
return err
}
return nil
}

@ -1,63 +0,0 @@
package protogen
import (
bluetooth "git.tcp.direct/kayos/prototooth"
"protomolecule/src/eros"
"sync"
"time"
)
/*
Why the Meta struct?
In theory we will have multiple types of scans in the future,
if that's the case this is going to become necessary very quickly
I've mocked up some commented out examples in the source code below.
*/
type Meta struct {
Scans map[int]*Scan
Mailbox chan Postcard
// // // Future Concepts // // //
// BLEScans map[int]*BLEScan
// LoraScans map[int]*LoraScan
// ZigScans map[int]*ZigScan
// WiFiScans map[int]*WiFiScan
// we definitely need this to be safe for concurrency
mu *sync.RWMutex
}
// Postcard will encapsulate interprocess communication messages that we send back to the main thread
type Postcard struct {
// From - the ScanID the message originated from
From int
// Stamp - the time the IPC message was created
Stamp time.Time
// Device ID - relevant DeviceID (if applicable) - Remember to use Scan.Devices[id] for easy referral
DeviceID int
// Command - the actual IPC command
Command string
// Arguments - augmenting arguments to the command
Arguments []string
}
// TODO: Form profiles on devices
// Scan represents an instance of a BLE scan
type Scan struct {
Count int
ID int
// Started the scan at this time
Started time.Time
// Activity was last seen at this time
Activity time.Time
// Device represents a bluetooth endpoints
Device map[int]*eros.Device
// Connection is an active connection to a bluetooth endpoint
Connection map[int]*bluetooth.Connection
/* Ignore is a map of devices to disregard during a scan,
by using the hardware address as a key. (needs disclaimer?) */
Ignore map[string]bool
// Gonna plan on mutexing scan operations just to be safe
mu *sync.RWMutex
}

@ -0,0 +1,33 @@
package scanStuff
import (
tea "github.com/charmbracelet/bubbletea"
"protomolecule/src/dust"
projVars "protomolecule/src/vars"
"protomolecule/src/decls"
"tinygo.org/x/bluetooth"
)
func Scanners(initialModel decls.Model, program *tea.Program) {
dust.FirstList()
dust.Must("enable BLE stack", projVars.ScanAdapter.Enable())
projVars.ScanAdapter.Scan(func(scanAdapter *bluetooth.Adapter, device bluetooth.ScanResult) {
if device.LocalName() != projVars.ScanList[device.Address.String()] {
projVars.ScanList[device.Address.String()] = device.LocalName()
// this is not the right way
//
initialModel.Choices = append(initialModel.Choices, device)
// initialModel.View()
// returns a string, this needs to be getting re-called by the renderer
// somehow..
// This might be a better way (pub/sub new choices?)
// https://github.com/charmbracelet/bubbletea/blob/master/examples/realtime/main.go#L41
program = tea.NewProgram(initialModel)
program.Start()
}
})
}

28
src/vars/projVars.go Normal file

@ -0,0 +1,28 @@
package projVars
import (
"flag"
"tinygo.org/x/bluetooth"
)
//The initial list of device in the area
var ScanList map[string]string
var InitResults []string
//hold the values for the initial ble scan results..
// ... i.e. the mac and broadcast name string
var C = make(chan bluetooth.ScanResult)
//the BLE adapter --duh
//var Adapter = bluetooth.DefaultAdapter
var ScanAdapter = bluetooth.DefaultAdapter
//Device to be targeted --not fully implemented
var Target = flag.String("t", "00:00:00:00:00:00", "Target device to attack")
//var attacker bluetooth.Addresser
//var connected bool
//var disconnected bool = true

@ -1,161 +0,0 @@
package wrath
import (
bluetooth "git.tcp.direct/kayos/prototooth"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"tinygo.org/x/bluetooth/rawterm"
projVars "protomolecule/src/config"
)
// used for offensive operations
/*
func targetPicture() bluetooth.MAC {
type targetMac bluetooth.MAC
for c, err := range projVars.WAFlag {
append(targetMac{})
}
println(targetMac)
target, err := bluetooth.ParseMAC(targetMac)
if err != nil {
log.Err(err).Msg("Error")
}
a := bluetooth.Address{
MACAddress: target,
}
return target
}
*/
func Start(targetUUID bluetooth.UUID) {
var sublog zerolog.Logger
// bluetooth.DefaultAdapter.Connect(targetPicture(), bluetooth.ConnectionParams{})
// TargetDevice, connectError := projVars.GlobalConfig.GetScanAdapter().Connect(result.Address, bluetooth.ConnectionParams{})
// Enable BLE interface.
err := projVars.GlobalConfig.GetScanAdapter().Enable()
if err != nil {
log.Error().
Err(err).
Msg("could not enable the BLE stack")
return
}
// The address to connect to. Set during scanning and read afterwards.
var foundDevice bluetooth.ScanResult
// Scan for NUS peripheral.
log.Info().Msg("Scanning...")
err = projVars.GlobalConfig.GetScanAdapter().Scan(func(adapter *bluetooth.Adapter, foundDevice bluetooth.ScanResult) {
if !foundDevice.AdvertisementPayload.HasServiceUUID(targetUUID) {
return
}
// Stop the scan.
err := projVars.GlobalConfig.GetScanAdapter().StopScan()
if err != nil {
// Unlikely, but we can't recover from this.
log.Panic().Err(err).
Msg("failed to stop scan")
}
})
if err != nil {
log.Error().Err(err).
Msg("failed to start scan")
return
}
// Found a device: print this event.
if name := foundDevice.LocalName(); name == "" {
sublog = log.With().Str("MAC", foundDevice.Address.String()).Logger()
} else {
sublog = log.With().Str("MAC", foundDevice.Address.String()).
Str("Name", foundDevice.LocalName()).Logger()
}
// Found a NUS peripheral. Connect to it.
device, err := projVars.GlobalConfig.GetScanAdapter().Connect(foundDevice.Address, bluetooth.ConnectionParams{})
if err != nil {
sublog.Error().Err(err).
Msg("Failed to connect")
return
}
// Connected. Look up the Nordic UART Service.
sublog.Info().Msg("Discovering services...")
services, err := device.DiscoverServices([]bluetooth.UUID{targetUUID})
if err != nil {
log.Error().Err(err).
Msg("Failed to discover the Nordic UART Service")
return
}
service := services[0]
// Discover all characteristics (we'll filter later)
chars, err := service.DiscoverCharacteristics([]bluetooth.UUID{})
if err != nil {
log.Error().Err(err).
Msg("Failed to discover RX and TX characteristics")
return
}
rx := chars[0]
tx := chars[1]
// Enable notifications to receive incoming data.
err = tx.EnableNotifications(func(value []byte) {
for _, c := range value {
rawterm.Putchar(c)
}
})
if err != nil {
sublog.Error().Err(err).
Msg("Failed to enable TX notifications")
return
}
sublog.Info().Msg("Connected. Exit console using Ctrl-X.")
rawterm.Configure()
defer rawterm.Restore()
var line []byte
for {
ch := rawterm.Getchar()
line = append(line, ch)
// Send the current line to the central.
if ch == '\x18' {
// The user pressed Ctrl-X, exit the program.
break
} else if ch == '\n' {
sendbuf := line // copy buffer
// Reset the slice while keeping the buffer in place.
line = line[:0]
// Send the sendbuf after breaking it up in pieces.
for len(sendbuf) != 0 {
// Chop off up to 20 bytes from the sendbuf.
partlen := 20
if len(sendbuf) < 20 {
partlen = len(sendbuf)
}
part := sendbuf[:partlen]
sendbuf = sendbuf[partlen:]
// This performs a "write command" aka "write without response".
_, err := rx.WriteWithoutResponse(part)
if err != nil {
sublog.Error().Err(err).
Msg("could not send:")
}
}
}
}
}