diff --git a/.gitignore b/.gitignore index e003572..73ac57a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,67 +1 @@ - -# Binaries for programs and plugins -main -*.exe -*.exe~ -*.dll -*.so -*.dylib -.idea -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ - -.DS_Store -*.[56789ao] -*.a[56789o] -*.pyc -._* -.nfs.* -[56789a].out -*~ -*.orig -*.rej -.*.swp -core -*.cgo*.go -*.cgo*.c -_cgo_* -_obj -_test -_testmain.go - -/VERSION.cache -/bin/ -/build.out -/doc/articles/wiki/*.bin -/goinstall.log -/last-change -/misc/cgo/life/run.out -/misc/cgo/stdio/run.out -/misc/cgo/testso/main -/pkg/ -/src/*.*/ -/src/cmd/cgo/zdefaultcc.go -/src/cmd/dist/dist -/src/cmd/go/internal/cfg/zdefaultcc.go -/src/cmd/go/internal/cfg/zosarch.go -/src/cmd/internal/objabi/zbootstrap.go -/src/go/build/zcgo.go -/src/go/doc/headscan -/src/runtime/internal/sys/zversion.go -/src/unicode/maketables -/test.out -/test/garbage/*.out -/test/pass.out -/test/run.out -/test/times.out - -# This file includes artifacts of Go build that should not be checked in. -# For files created by specific development environment (e.g. editor), -# use alternative ways to exclude files from git. -# For example, set up .git/info/exclude or use a global .gitignore. \ No newline at end of file +ios_video_stream \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..460ebad --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 danielpaulus + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5a8f4ee --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +TARGET = ios_video_stream + +all: $(TARGET) + +$(TARGET): main.go server.go go.sum + go build -o $(TARGET) . + +go.sum: + go get + go get . + +clean: + $(RM) $(TARGET) go.sum diff --git a/README.md b/README.md index 84ea871..350418a 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,20 @@ -### Operating System indepedent implementation for Quicktime Screensharing for iOS devices :-) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +## 1. What is this? +This is an altered version [Quicktime Video Hack](https://github.com/danielpaulus/quicktime_video_hack) -run the `qvh` tool to get details :-) +It has been altered in the following ways: -### MAC OS X LIBUSB -- IMPORTANT -Make sure to use either this fork `https://github.com/GroundControl-Solutions/libusb` -or a LibUsb version BELOW 1.0.20 or iOS devices won't be found on Mac OS X. -[See Github Issue](https://github.com/libusb/libusb/issues/290) \ No newline at end of file +1. All gstreamer code has been removed +2. The h264 stream is not decoded at all. Instead it is send via ZMQ elsewhere for decoding. +3. It receives decoded frames back via ZMQ also in the form of a series of jpegs. +4. Those jpegs are then served out via http ( both latest jpeg, and streaming websocket ) + +## 2. Installation + +1. `make` + +## 3. Usage + +1. Start a decoder +2. `./ios_video_stream stream [zmq spec to send to] [zmq spec to recv from] [--udid=] diff --git a/go.mod b/go.mod index df7f4bc..5045b5b 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,17 @@ -module github.com/danielpaulus/quicktime_video_hack +module github.com/nanoscopic/ios_video_stream go 1.13 require ( + github.com/danielpaulus/go-ios v0.0.0-20190926190740-cc977db05eea // indirect github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 // indirect - github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750 + github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750 // indirect + github.com/gorilla/websocket v1.4.2 + github.com/nanoscopic/ujsonin v1.5.0 + github.com/pebbe/zmq4 v1.2.0 // indirect + github.com/pion/rtp v1.1.4 // indirect + github.com/pion/webrtc/v2 v2.1.11 // indirect github.com/sirupsen/logrus v1.4.2 + github.com/stretchr/testify v1.4.0 // indirect + go.nanomsg.org/mangos/v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 2622fa8..b0afc0f 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,97 @@ +github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/danielpaulus/go-ios v0.0.0-20190926190740-cc977db05eea/go.mod h1:/I8WMN9JIALHl5vtg939USw/gDASCwlC9D+K10QdGB4= +github.com/danielpaulus/gst v0.0.0-20200201205042-e6d2974fceb8/go.mod h1:JbhjLST5AaUXpKQK65g9144BK8QHftbpuFoYuhDuONw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gdamore/optopia v0.2.0/go.mod h1:YKYEwo5C1Pa617H7NlPcmQXl+vG6YnSSNB44n8dNL0Q= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750 h1:DVKHLo3yE4psTjD9aM2pY7EHoicaQbgmaxxvvHC6ZSM= github.com/google/gousb v0.0.0-20190812193832-18f4c1d8a750/go.mod h1:Tl4HdAs1ThE3gECkNwz+1MWicX6FXddhJEw7L8jRDiI= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/lijo-jose/glib v0.0.0-20191012030101-93ee72d7d646/go.mod h1:wzypjnJX+g/LKnKDVvJni/u0gNlQestTwv6Kt/Qf3fk= +github.com/lucas-clemente/quic-go v0.7.1-0.20190401152353-907071221cf9/go.mod h1:PpMmPfPKO9nKJ/psF49ESTAGQSdfXxlg1otPbEB2nOw= +github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= +github.com/nanoscopic/ujsonin v1.1.0 h1:w2IoDngW3p7K1Dj2xqrSoX/G+3sGEmczSicL750nzVI= +github.com/nanoscopic/ujsonin v1.1.0/go.mod h1:Q/8hEeydgTJwpaZyL3UREl3U44P01CI5VAL6ueB47dI= +github.com/nanoscopic/ujsonin v1.5.0 h1:pnbQ6jHsfDm9O/wWLkszM3n73ETeHrsutf7LV3gTTro= +github.com/nanoscopic/ujsonin v1.5.0/go.mod h1:FyHvuWes/DhijYGBTtQB74enYOLiHodd3M3waNV3gWU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pebbe/zmq4 v1.2.0/go.mod h1:7N4y5R18zBiu3l0vajMUWQgZyjv464prE8RCyBcmnZM= +github.com/pion/datachannel v1.4.12/go.mod h1:Ulrx2j4r8c0Za5ltWFv/hZvSpc3ZpvOvcz46tvnt+PY= +github.com/pion/dtls v1.5.2/go.mod h1:v4ULmyyV65geAZQBBckCjgMhmngTqz7HQVsQVYnfkGo= +github.com/pion/ice v0.7.1/go.mod h1:fPnWLWO3B83fJmO6Sci5Mv3ypN4Vd956Py4JlbJfVwU= +github.com/pion/logging v0.2.1/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/mdns v0.0.3/go.mod h1:VrN3wefVgtfL8QgpEblPUC46ag1reLIfpqekCnKunLE= +github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k= +github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM= +github.com/pion/rtp v1.1.3/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= +github.com/pion/rtp v1.1.4/go.mod h1:/l4cvcKd0D3u9JLs2xSVI95YkfXW87a3br3nqmVtSlE= +github.com/pion/sctp v1.7.2/go.mod h1:HlTD+15FeLYYQTTDO35uKEeRLVq5L2AY/ef6ZSvpIXc= +github.com/pion/sdp/v2 v2.3.1/go.mod h1:jccXVYW0fuK6ds2pwKr89SVBDYlCjhgMI6nucl5R5rA= +github.com/pion/srtp v1.2.6/go.mod h1:rd8imc5htjfs99XiEoOjLMEOcVjME63UHx9Ek9IGst0= +github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M= +github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE= +github.com/pion/transport v0.8.9/go.mod h1:lpeSM6KJFejVtZf8k0fgeN7zE73APQpTF83WvA1FVP8= +github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8= +github.com/pion/turn v1.4.0/go.mod h1:aDSi6hWX/hd1+gKia9cExZOR0MU95O7zX9p3Gw/P2aU= +github.com/pion/webrtc/v2 v2.1.11/go.mod h1:spr7E544asF+Y3ajLasHi9fSJTiDjUERRBeTeRimlfE= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +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/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tmobile/stf_ios_mirrorfeed/mirrorfeed/mods/mjpeg v0.0.0-20200109223406-fc86e617c1c5/go.mod h1:g2Z8fnPWJ5xiSDsZieY3fzGGcAQPJI6gO8fSWAIx+q8= +go.nanomsg.org/mangos/v3 v3.0.1 h1:xR8nca0ZeAvwsoRWjeEHuR2/B0N+Po/ZJpGNCpDz6To= +go.nanomsg.org/mangos/v3 v3.0.1/go.mod h1:RxVwsn46YtfJ74mF8MeVo+MFjg545KCI50NuZrFXmzc= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/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-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0= +golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +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= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= diff --git a/log/dtrace-probes.md b/log/dtrace-probes.md deleted file mode 100644 index 67907ee..0000000 --- a/log/dtrace-probes.md +++ /dev/null @@ -1,92 +0,0 @@ -I had a look at a few USB API Calls: - - -sudo dtrace -n '*:*:*IOUSBDevice*:entry { stack(); }' - -[iOS8 "com.apple.mobile.screenshotr" is replaced with the "com.apple.cmio.iOSScreenCaptureAssistant" service · Issue #122 · libimobiledevice/libimobiledevice · GitHub](https://github.com/libimobiledevice/libimobiledevice/issues/122) - -iOSScreenCaptureAssistant -https://www.youtube.com/watch?v=A9gqzn3XcDM&feature=youtu.be - -/System/Library/Frameworks/CoreMediaIO.framework/Versions/A/Resources/iOSScreenCapture.plugin/Contents/Resources/iOSScreenCaptureAssistant - - - - -sudo dtrace -n '*:*:*IOUSBDevice*:entry/execname=="iOSScreenCapture"/ { stack(); }' - - - -sudo dtrace -n '*:*:*IOUSBDevice*:entry/execname=="iOSScreenCapture"/ { printf("--%s--", execname); }' - - - -sudo dtrace -n '*:*IOUSBFamily*:*:entry/execname=="iOSScreenCapture"/ { printf("--%s--", execname); }' - -sudo dtrace -n '*:*:*ControlRequest*:entry/execname=="iOSScreenCapture"/ { printf("--%s--", probefunc); }' - - - - - - -that is how you write data to usb: [objective c - USB device send/receive data - Stack Overflow](https://stackoverflow.com/questions/41038150/usb-device-send-receive-data) -```IOReturn WriteToDevice(IOUSBDeviceInterface **dev, UInt16 deviceAddress, - UInt16 length, UInt8 writeBuffer[]) -{ - - IOUSBDevRequest request; - request.bmRequestType = USBmakebmRequestType(kUSBOut, kUSBVendor, - kUSBDevice); - request.bRequest = 0xa0; - request.wValue = deviceAddress; - request.wIndex = 0; - request.wLength = length; - request.pData = writeBuffer; - - return (*dev)->DeviceRequest(dev, &request); -} -``` -so let's check out all the deviceRequests then - - -sudo dtrace -n '*:*:*DeviceRequest*:entry/execname=="iOSScreenCapture"/ { tracemem(arg1, 8); }' -sudo dtrace -n '*:*:*DeviceRequest*:entry { tracemem(arg1, 8); }' -sudo dtrace -n '*:*:*DeviceRequest*:entry/execname=="iOSScreenCapture"/ { printf("devpointer:%#010x -- struct_ptr: %#010x", arg0, arg1); }' - -die methoden hier: -[IOUSBFamily/IOUSBInterfaceUserClient.h at master · opensource-apple/IOUSBFamily · GitHub](https://github.com/opensource-apple/IOUSBFamily/blob/master/IOUSBUserClient/Headers/IOUSBInterfaceUserClient.h) - - -who is setting the config? -sudo dtrace -n '*:*:*SetConfiguration*:entry { printf("c:%s-b:%d", execname, arg1); ustack(); }' -``` - 0 86572 _ZN21IOUSBDeviceUserClient16SetConfigurationEh:entry c:usbmuxd-b:6 - libsystem_kernel.dylib`mach_msg_trap+0xa - IOKit`io_connect_method+0x176 - IOKit`IOConnectCallScalarMethod+0x4c - IOUSBLib`IOUSBDeviceClass::SetConfiguration(unsigned char)+0x51 - usbmuxd`0x0000000104d6a02d+0x339 - IOKit`IODispatchCalloutFromCFMessage+0x164 - CoreFoundation`__CFMachPortPerform+0x11a - CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__+0x29 - CoreFoundation`__CFRunLoopDoSource1+0x20f - CoreFoundation`__CFRunLoopRun+0x9dc - CoreFoundation`CFRunLoopRunSpecific+0x1c7 - CoreFoundation`CFRunLoopRun+0x28 - usbmuxd`0x0000000104d5d7b3+0x59e - libdyld.dylib`start+0x1 - usbmuxd`0x2 -``` - -seems like usbmuxd is doing it, so let's check it out:/System/Library/PrivateFrameworks/MobileDevice.framework/Versions/A/Resources/usbmuxd -/Library/Preferences/com.apple.usbmuxd.plis -interesting strings: AddNewInterface, FoundNewInterfaces -sub_10000e02d - - -ups: -first -![a0b55237.png](:storage/49993860-1195-4bd7-9568-ea254440d571/a0b55237.png) -and then -![1b82ea57.png](:storage/49993860-1195-4bd7-9568-ea254440d571/1b82ea57.png) diff --git a/log/ibridgecontrol.entitlements b/log/ibridgecontrol.entitlements deleted file mode 100644 index 0427ef7..0000000 --- a/log/ibridgecontrol.entitlements +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - com.apple.ibridge.control - - - \ No newline at end of file diff --git a/log/usb-reverse.md b/log/usb-reverse.md deleted file mode 100644 index 11c1acc..0000000 --- a/log/usb-reverse.md +++ /dev/null @@ -1,113 +0,0 @@ - - -## Notes: - doing some of these things on mac will result in the kernel logging: - "missing entitlement com.apple.ibridge.control" could this be it? - Seems like there is no real way to do all this on a Mac, on the other hand, you have - Quicktime there so why would you? :-D - -## Steps - -run `main.go activate` to do the following stuff for you: -On a Mac you can see QT is changing the device usb config when you open the video mirroring. -That config however is usually missing on the ios device, to make it appear issue the following control transfer to the device: -0000 40 52 00 00 02 00 00 00 @R...... -The device will then disconnect itself, and reconnect with an additional USB config. -USBMUXD usually activates the new config automatically and now it is good to use with two additional bulk endpoints -providing a video stream. - - - - -## random observations: - -What i can see from the ios qt -0000 00 01 20 00 12 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 cd f9 6a 02 00 00 00 00 00 20 41 14 02 12 80 00 ..j...... A..... -0020 80 06 00 01 00 00 12 00 ........ - - -0000 00 01 20 01 12 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 cd f9 6a 02 00 00 00 00 00 20 41 14 02 12 80 00 ..j...... A..... -0020 12 01 00 02 00 00 00 40 ac 05 a8 12 06 10 01 02 .......@........ -0030 03 05 .. - -last byte is bnumconfig - - -libusb normal getActiveConfig Req - -0000 00 01 20 00 01 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 e5 b1 8e 02 00 00 00 00 00 20 41 14 02 17 80 00 ......... A..... -0020 80 08 00 00 00 00 01 00 ........ - -libusb normal getDeviceDescriptor Req -0000 00 01 20 00 01 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 95 c6 8e 02 00 00 00 00 00 20 41 14 02 17 80 00 ......... A..... -0020 80 08 00 00 00 00 01 00 ........ - -doc on control transfers -http://www.jungo.com/st/support/documentation/windriver/802/wdusb_man_mhtml/node55.html - -reading about clear feature https://www.beyondlogic.org/usbnutshell/usb6.shtml -seems like clear feature, then 48 bytes ping can be reading - -first: -Clear feature req: -0000 00 01 20 00 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 d1 03 6b 02 00 00 00 00 00 20 41 14 02 12 00 00 ..k...... A..... -0020 02 01 00 00 86 00 00 00 ........ - -0000 00 01 20 00 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 a3 c0 98 02 00 00 00 00 00 00 20 14 02 0a 00 00 .......... ..... -0020 02 01 00 00 86 00 00 00 ........ - -0000 00 01 20 00 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 b7 fb 98 02 00 00 00 00 00 00 20 14 02 0a 00 00 .......... ..... -0020 02 01 00 00 86 00 00 00 ........ - - - -clear feature res: -0000 00 01 20 01 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 d1 03 6b 02 00 00 00 00 00 20 41 14 02 12 00 00 ..k...... A..... - -0000 00 01 20 01 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 a3 c0 98 02 00 00 00 00 00 00 20 14 02 0a 00 00 .......... ..... - - -second: -clear feature req: -0000 00 01 20 00 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 d3 03 6b 02 00 00 00 00 00 20 41 14 02 12 00 00 ..k...... A..... -0020 02 01 00 00 05 00 00 00 ........ - -0000 00 01 20 00 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 a5 c0 98 02 00 00 00 00 00 00 20 14 02 0a 00 00 .......... ..... -0020 02 01 00 00 05 00 00 00 ........ - - - - -clear feature res: -0000 00 01 20 01 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 d3 03 6b 02 00 00 00 00 00 20 41 14 02 12 00 00 ..k...... A..... - -0000 00 01 20 01 00 00 00 00 00 00 00 00 00 00 00 00 .. ............. -0010 a5 c0 98 02 00 00 00 00 00 00 20 14 02 0a 00 00 .......... ..... - - - - - -then a ping is exchanged: -read: -0020 10 00 00 00 67 6e 69 70 00 00 00 00 01 00 00 00 ....gnip........ - -0020 10 00 00 00 67 6e 69 70 00 00 00 00 01 00 00 00 ....gnip........ - -write: -0020 10 00 00 00 67 6e 69 70 01 00 00 00 01 00 00 00 ....gnip........ - -0020 10 00 00 00 67 6e 69 70 01 00 00 00 01 00 00 00 ....gnip........ - diff --git a/main.go b/main.go index 918d74a..db4a820 100644 --- a/main.go +++ b/main.go @@ -1,88 +1,89 @@ package main import ( - "github.com/danielpaulus/quicktime_video_hack/usb" - "github.com/docopt/docopt-go" - log "github.com/sirupsen/logrus" + "flag" + "fmt" + "os" + "os/signal" + "time" + + "go.nanomsg.org/mangos/v3" + "go.nanomsg.org/mangos/v3/protocol/pull" + // register transports + _ "go.nanomsg.org/mangos/v3/transport/all" + + log "github.com/sirupsen/logrus" ) func main() { - usage := `Q.uickTime V.ideo H.ack or qvh client v0.01 - If you do not specify a udid, the first device will be taken by default. - -Usage: - qvh devices - qvh activate - qvh dumpraw - -Options: - -h --help Show this screen. - --version Show version. - -u=, --udid UDID of the device. - -o=, --output - ` - arguments, _ := docopt.ParseDoc(usage) - //TODO: add verbose switch to conf this - log.SetLevel(log.DebugLevel) - udid, _ := arguments.String("--udid") - //TODO:add device selection here - log.Info(udid) - - cleanup := usb.Init() - defer cleanup() - deviceList, err := usb.FindIosDevices() - - if err != nil { - log.Fatal("Error finding iOS Devices", err) - } + var udid = flag.String( "udid" , "" , "Device UDID" ) + var streamCmd = flag.Bool( "stream" , false , "Stream jpegs" ) + var pullSpec = flag.String( "pullSpec" , "tcp://127.0.0.1:7879", "NanoMsg spec to pull jpeg frames from" ) + var iface = flag.String( "interface", "none" , "Network interface to listen on" ) + var port = flag.String( "port" , "8000" , "Network port to listen on" ) + var verbose = flag.Bool( "v" , false , "Verbose Debugging" ) + var secure = flag.Bool( "secure" , false , "Secure HTTPS mode" ) + var cert = flag.String( "cert" , "https-server.crt" , "Cert for HTTP" ) + var key = flag.String( "key" , "https-server.key" , "Key for HTTPS" ) + var coordinator= flag.String( "coordinator", "127.0.0.1:8027" , "Coordinator control address" ) + + flag.Parse() + + log.SetFormatter(&log.JSONFormatter{}) - devicesCommand, _ := arguments.Bool("devices") - if devicesCommand { - devices(deviceList) - return - } + if *verbose { + log.Info("Set Debug mode") + log.SetLevel(log.DebugLevel) + } - activateCommand, _ := arguments.Bool("activate") - if activateCommand { - activate(deviceList) - return - } - - rawStreamCommand, _ := arguments.Bool("dumpraw") - if rawStreamCommand { - dev := deviceList[0] - usb.StartReading(dev) - return - } + if *streamCmd { + stream( *pullSpec, *udid, *iface, *port, *secure, *cert, *key, *coordinator ) + } else { + flag.Usage() + } } -// Just dump a list of what was discovered to the console -func devices(devices []usb.IosDevice) { - log.Info("iOS Devices with UsbMux Endpoint:") - - output := usb.PrintDeviceDetails(devices) - log.Info(output) +func stream( pullSpec string, udid string, tunName string, port string, secure bool, cert string, key string, coordinator string ) { + pullSock := setup_nanomsg_sockets( pullSpec ) + + stopChannel := make( chan bool ) + stopChannel2 := make( chan bool ) + waitForSigInt( stopChannel, stopChannel2 ) + + startJpegServer( pullSock, stopChannel2, port, tunName, secure, cert, key, coordinator, udid ) + + <- stopChannel } -// This command is for testing if we can enable the hidden Quicktime device config -func activate(devices []usb.IosDevice) { - log.Info("iOS Devices with UsbMux Endpoint:") - - output := usb.PrintDeviceDetails(devices) - log.Info(output) - err := usb.EnableQTConfig(devices) - if err != nil { - log.Fatal("Error enabling QT config", err) - } +func setup_nanomsg_sockets( pullSpec string ) ( pullSock mangos.Socket ) { + var err error + + if pullSock, err = pull.NewSocket(); err != nil { + log.WithFields( log.Fields{ + "type": "err_socket_new", + "zmq_spec": pullSpec, + "err": err, + } ).Fatal("Socket new error") + } + pullSock.SetOption( mangos.OptionRecvDeadline, time.Duration.Seconds( 1 ) ) + if err = pullSock.Listen(pullSpec); err != nil { + log.WithFields( log.Fields{ + "type": "err_socket_bind", + "spec": pullSpec, + "err": err, + } ).Fatal("Socket bind error") + } + return pullSock +} - qtDevices, err := usb.FindIosDevicesWithQTEnabled() - if err != nil { - log.Fatal("Error finding QT Devices", err) - } - qtOutput := usb.PrintDeviceDetails(qtDevices) - if len(qtDevices) != len(devices) { - log.Warnf("Less qt devices (%d) than plain usbmux devices (%d)", len(qtDevices), len(devices)) - } - log.Info("iOS Devices with QT Endpoint:") - log.Info(qtOutput) +func waitForSigInt( stopChannel chan bool, stopChannel2 chan bool ) { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func() { + for sig := range c { + fmt.Printf("Got signal %s\n", sig) + go func() { stopChannel2 <- true }() + stopChannel <- true + } + }() } diff --git a/server.go b/server.go new file mode 100644 index 0000000..caa6018 --- /dev/null +++ b/server.go @@ -0,0 +1,516 @@ +package main + +import ( + "bytes" + "fmt" + "html/template" + //"image" + _ "image/jpeg" + //"io" + "log" + "net" + "net/http" + "os" + "strings" + "sync" + //"time" + + "github.com/gorilla/websocket" + + "go.nanomsg.org/mangos/v3" + // register transports + _ "go.nanomsg.org/mangos/v3/transport/all" + uj "github.com/nanoscopic/ujsonin/mod" +) + +func callback( r *http.Request ) bool { + return true +} + +var upgrader = websocket.Upgrader { + CheckOrigin: callback, +} + +type ImgMsg struct { + imgNum int + msg string + data []byte +} + +type ImgType struct { + rawData []byte +} + +const ( + WriterStop = iota +) + +type WriterMsg struct { + msg int +} + +type MainMsg struct { + msg int +} + +const ( + BeginDiscard = iota + EndDiscard +) + +type Stats struct { + recv int + dumped int + sent int + socketConnected bool + waitCnt int +} + +func startJpegServer( inSock mangos.Socket, stopChannel chan bool, mirrorPort string, tunName string, secure bool, cert string, key string, coordinator string, udid string ) { + var err error + + ifaces, err := net.Interfaces() + if err != nil { + fmt.Printf( err.Error() ) + os.Exit( 1 ) + } + + var listen_addr string + if tunName == "none" { + fmt.Printf("No tunnel specified; listening on all interfaces\n") + listen_addr = ":" + mirrorPort + } else { + foundInterface := false + for _, iface := range ifaces { + addrs, err := iface.Addrs() + if err != nil { + fmt.Printf( err.Error() ) + os.Exit( 1 ) + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + default: + fmt.Printf("Unknown type\n") + } + if iface.Name == tunName { + str := ip.String() + if !strings.Contains(str,":") { + listen_addr = str + ":" + mirrorPort + foundInterface = true + } + } + } + } + if foundInterface == false { + fmt.Printf( "Could not find interface %s\n", tunName ) + os.Exit( 1 ) + } + } + + imgCh := make(chan ImgMsg, 60) + mainCh := make( chan MainMsg, 4 ) + + lock := sync.RWMutex{} + var stats Stats = Stats{} + stats.recv = 0 + stats.dumped = 0 + stats.socketConnected = false + + statLock := sync.RWMutex{} + + sentSize := false + discard := true + + go func() { + imgnum := 1 + + LOOP: + for { + select { + case <- stopChannel: + fmt.Printf("Server channel got stop message\n") + break LOOP + case msg := <- mainCh: + if msg.msg == BeginDiscard { + discard = true + } else if msg.msg == EndDiscard { + discard = false + } + default: // this makes the above read from stopChannel non-blocking + } + + msg, err := inSock.RecvMsg() + + fmt.Printf("Got incoming frame\n") + + if err != nil && err == mangos.ErrRecvTimeout { + continue + } + + if discard && sentSize { + statLock.Lock() + stats.dumped++ + statLock.Unlock() + + msg.Free() + continue + } + + clickScale := 1000 + + imgMsg := ImgMsg{} + + // image is prepended by some JSON metadata + if msg.Body[0] == '{' { + endi := strings.Index( string(msg.Body), "}" ) + //fmt.Printf( "out:[" + string( msg.Body[:endi+1] ) + "]" ) + root, left := uj.Parse( msg.Body ) + if ( len(msg.Body ) - len( left ) - 1 ) != endi { + fmt.Printf( "size mistmatched what was parsed: %d != %d\n", endi, len( msg.Body ) - len(left) - 1 ) + } + imgMsg.data = left + + ow := root.Get("ow").Int() + //oh := root.Get("og").Int() + dw := root.Get("dw").Int() + dh := root.Get("dh").Int() + if ow != dw { + clickScale = ow / dw * 1000 + } + + imgMsg.msg = fmt.Sprintf("Width: %d, Height: %d, Clickscale: %d, Size: %d\n", dw, dh, clickScale, len( msg.Body ) ) + + if !sentSize { + dataReader := bytes.NewBuffer( []byte( fmt.Sprintf( `{"type":"frame1","width":%d,"height":%d,"clickScale":%d,"uuid":"%s"}`, dw, dh, clickScale, udid ) ) ) + http.Post("http://"+coordinator+"/frame", "application/json", dataReader ) + sentSize = true + } + } else { + imgMsg.data = msg.Body + } + + if !discard { + imgMsg.imgNum = imgnum + imgCh <- imgMsg + msg.Free() + } else { + msg.Free() + } + + statLock.Lock() + stats.recv++ + statLock.Unlock() + + imgnum++ + } + }() + + startServer( imgCh, mainCh, &lock, &statLock, &stats, listen_addr, secure, cert, key ) +} + +func startWriter(ws *websocket.Conn,imgCh <-chan ImgMsg,writerCh <-chan WriterMsg,lock *sync.RWMutex, statLock *sync.RWMutex, stats *Stats) { + go func() { + var running bool = true + statLock.Lock() + stats.socketConnected = true + statLock.Unlock() + for { + select { + case imgMsg := <- imgCh: + // Keep receiving images till there are no more to receive + stop := false + for { + select { + case imgMsg = <- imgCh: + + default: + stop = true + } + if stop { break } + } + + msg := []byte(imgMsg.msg) + imgBytes := imgMsg.data + + lock.Lock() + ws.WriteMessage(websocket.TextMessage, msg) + ws.WriteMessage(websocket.BinaryMessage, imgBytes ) + lock.Unlock() + case controlMsg := <- writerCh: + if controlMsg.msg == WriterStop { + running = false + } + } + if running == false { + break + } + } + statLock.Lock() + stats.socketConnected = false + statLock.Unlock() + }() +} + +func startServer( imgCh <-chan ImgMsg, mainCh chan<- MainMsg, lock *sync.RWMutex, statLock *sync.RWMutex, stats *Stats, listen_addr string, secure bool, cert string, key string ) (*http.Server) { + fmt.Printf("Listening on %s\n", listen_addr ) + + echoClosure := func( w http.ResponseWriter, r *http.Request ) { + handleEcho( w, r, imgCh, mainCh, lock, statLock, stats ) + } + statsClosure := func( w http.ResponseWriter, r *http.Request ) { + handleStats( w, r, statLock, stats ) + } + rootClosure := func( w http.ResponseWriter, r *http.Request ) { + handleRoot( w, r, secure ) + } + + server := &http.Server{ Addr: listen_addr } + http.HandleFunc( "/echo", echoClosure ) + http.HandleFunc( "/echo/", echoClosure ) + http.HandleFunc( "/", rootClosure ) + http.HandleFunc( "/stats", statsClosure ) + go func() { + if secure { + server.ListenAndServeTLS( cert, key ); + } else { + server.ListenAndServe() + } + }() + return server +} + +func handleStats( w http.ResponseWriter, r *http.Request, statLock *sync.RWMutex, stats *Stats ) { + statLock.Lock() + recv := stats.recv + dumped := stats.dumped + socketConnected := stats.socketConnected + statLock.Unlock() + waitCnt := stats.waitCnt + + var socketStr string = "no" + if socketConnected { + socketStr = "yes" + } + + fmt.Fprintf( w, "Received: %d
\nDumped: %d
\nSocket Connected: %s
\nWait Count: %d
\n", recv, dumped, socketStr, waitCnt ) +} + +func handleEcho( w http.ResponseWriter, r *http.Request,imgCh <-chan ImgMsg,mainCh chan<- MainMsg, lock *sync.RWMutex, statLock *sync.RWMutex, stats *Stats ) { + c, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Print("Upgrade error:", err) + return + } + defer c.Close() + fmt.Printf("Received connection\n") + welcome(c) + + writerCh := make(chan WriterMsg, 2) + + // stop discarding images + mainCh <- MainMsg{ msg: EndDiscard } + + stopped := false + + c.SetCloseHandler( func( code int, text string ) error { + stopped = true + writerCh <- WriterMsg{ msg: WriterStop } + mainCh <- MainMsg{ msg: BeginDiscard } + return nil + } ) + + + startWriter(c,imgCh,writerCh,lock,statLock,stats) + for { + mt, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + log.Printf("recv: %s", message) + lock.Lock() + err = c.WriteMessage(mt, message) + lock.Unlock() + if err != nil { + log.Println("write:", err) + break + } + } + + // send WriterMsg to terminate writer + if !stopped { + writerCh <- WriterMsg{ msg: WriterStop } + mainCh <- MainMsg{ msg: BeginDiscard } + } +} + +func welcome( c *websocket.Conn ) ( error ) { + msg := ` +{ + "version":1, + "length":24, + "pid":12733, + "realWidth":750, + "realHeight":1334, + "virtualWidth":375, + "virtualHeight":667, + "orientation":0, + "quirks":{ + "dumb":false, + "alwaysUpright":true, + "tear":false + } +}` + return c.WriteMessage( websocket.TextMessage, []byte(msg) ) +} + +func handleRoot( w http.ResponseWriter, r *http.Request, secure bool ) { + if secure { + rootTpl.Execute( w, "wss://"+r.Host+"/echo" ) + } else { + rootTpl.Execute( w, "ws://"+r.Host+"/echo" ) + } +} + +var rootTpl = template.Must(template.New("").Parse(` + + + + + + + + + + + + + + +
+ + +
+ + +
+ + +
+
+
+
+ + +`)) \ No newline at end of file diff --git a/usb/constants.go b/usb/constants.go deleted file mode 100644 index 11e0b7c..0000000 --- a/usb/constants.go +++ /dev/null @@ -1,8 +0,0 @@ -package usb - -var need = [...] byte{ - 0x14, 0x00, 0x00, 0x00, - 0x6E, 0x79, 0x73, 0x61, - 0x50, 0x10, 0x56, 0x13, - 0x01, 0x00, 0x00, 0x00, - 0x64, 0x65, 0x65, 0x6E} diff --git a/usb/discovery.go b/usb/discovery.go deleted file mode 100644 index 439a402..0000000 --- a/usb/discovery.go +++ /dev/null @@ -1,172 +0,0 @@ -package usb - -import ( - "fmt" - log "github.com/sirupsen/logrus" - "strings" -) -import "github.com/google/gousb" - -type IosDevice struct { - usbDevice *gousb.Device - SerialNumber string - ProductName string - UsbMuxConfigIndex int - QTConfigIndex int -} - -const ( - //Interesting, maybe the subclass type Application was chosen intentionally by Apple - //Because this config enables the basic communication between MacOSX Apps and iOS Devices over USBMuxD - UsbMuxSubclass gousb.Class = gousb.ClassApplication - //You can observe this config being activated as soon as you enable iOS Screen Sharing in Quicktime (That's how I - //found out :-D ) - QuicktimeSubclass gousb.Class = 0x2A -) - -// FindIosDevices finds iOS devices connected on USB ports by looking for their -// USBMux compatible Bulk Endpoints and QuickTime Video Stream compatible Bulk Endpoints -func FindIosDevicesWithQTEnabled() ([]IosDevice, error) { - return findIosDevices(isValidIosDeviceWithActiveQTConfig) -} - -// FindIosDevices finds iOS devices connected on USB ports by looking for their -// USBMux compatible Bulk Endpoints -func FindIosDevices() ([]IosDevice, error) { - return findIosDevices(isValidIosDevice) -} - -var ctx *gousb.Context - -func Init() func() { - ctx = gousb.NewContext() - return func() { - err := ctx.Close() - if err != nil { - log.Fatal("Failed while closing usb Context" + err.Error()) - } - } -} - -func findIosDevices(validDeviceChecker func(desc *gousb.DeviceDesc) bool) ([]IosDevice, error) { - devices, err := ctx.OpenDevices(func(desc *gousb.DeviceDesc) bool { - // this function is called for every device present. - // Returning true means the device should be opened. - return validDeviceChecker(desc) - }) - if err != nil { - return nil, err - } - iosDevices, err := mapToIosDevice(devices) - if err != nil { - return nil, err - } - - return iosDevices, nil -} - -func mapToIosDevice(devices []*gousb.Device) ([]IosDevice, error) { - iosDevices := make([]IosDevice, len(devices)) - for i, d := range devices { - serial, err := d.SerialNumber() - if err != nil { - return nil, err - } - product, err := d.Product() - if err != nil { - return nil, err - } - muxConfigIndex, qtConfigIndex := findConfigurations(d.Desc) - iosDevice := IosDevice{d, serial, product, muxConfigIndex, qtConfigIndex} - iosDevices[i] = iosDevice - } - return iosDevices, nil -} - -func PrintDeviceDetails(devices []IosDevice) string { - var sb strings.Builder - for _, d := range devices { - sb.WriteString(d.String()) - } - return sb.String() -} - -func isValidIosDevice(desc *gousb.DeviceDesc) bool { - muxConfigIndex, _ := findConfigurations(desc) - if muxConfigIndex == -1 { - return false - } - return true -} - -func isValidIosDeviceWithActiveQTConfig(desc *gousb.DeviceDesc) bool { - _, qtConfigIndex := findConfigurations(desc) - if qtConfigIndex == -1 { - return false - } - return true -} - -func findConfigurations(desc *gousb.DeviceDesc) (int, int) { - var muxConfigIndex = -1 - var qtConfigIndex = -1 - - for _, v := range desc.Configs { - if isMuxConfig(v) && !isQtConfig(v) { - muxConfigIndex = v.Number - log.Debugf("Found MuxConfig %d for Device %s", muxConfigIndex, desc.String()) - } - if isQtConfig(v) { - qtConfigIndex = v.Number - log.Debugf("Found QTConfig %d for Device %s", qtConfigIndex, desc.String()) - } - } - return muxConfigIndex, qtConfigIndex -} - -func isQtConfig(confDesc gousb.ConfigDesc) bool { - b, _ := findInterfaceForSubclass(confDesc, QuicktimeSubclass) - return b -} - -func isMuxConfig(confDesc gousb.ConfigDesc) bool { - b, _ := findInterfaceForSubclass(confDesc, UsbMuxSubclass) - return b -} - -func findInterfaceForSubclass(confDesc gousb.ConfigDesc, subClass gousb.Class) (bool, int) { - for i := range confDesc.Interfaces { - //usually the interfaces we care about have only one altsetting - isVendorClass := confDesc.Interfaces[i].AltSettings[0].Class == gousb.ClassVendorSpec - isCorrectSubClass := confDesc.Interfaces[i].AltSettings[0].SubClass == subClass - if isVendorClass && isCorrectSubClass { - return true, confDesc.Interfaces[i].Number - } - } - return false, -1 -} - -func (d *IosDevice) String() string { - return fmt.Sprintf("'%s' %s serial: %s", d.ProductName, d.usbDevice.String(), d.SerialNumber) -} - -//Always call this when you're done recording your video to -//put the device back into non-video mode -func (d *IosDevice) enableUsbMuxConfig() error { - config, err := d.usbDevice.Config(d.UsbMuxConfigIndex) - if err != nil { - return err - } - return config.Close() -} - -//This enables the config needed for grabbing video of the device -//it should open two additional bulk endpoints where video frames -//will be received -func (d *IosDevice) enableQuickTimeConfig() (*gousb.Config, error) { - config, err := d.usbDevice.Config(d.QTConfigIndex) - if err != nil { - return nil, err - } - return config, nil -} diff --git a/usb/usbvideostream.go b/usb/usbvideostream.go deleted file mode 100644 index a40a35c..0000000 --- a/usb/usbvideostream.go +++ /dev/null @@ -1,106 +0,0 @@ -package usb - -import ( - "github.com/google/gousb" - log "github.com/sirupsen/logrus" - "time" -) - -// EnableQTConfig enables the hidden QuickTime Device configuration that will expose two new bulk endpoints. -// We will send a control transfer to the device via USB which will cause the device to disconnect and then -// re-connect with a new device configuration. Usually the usbmuxd will automatically enable that new config -// as it will detect it as the device's preferredConfig. -func EnableQTConfig(devices []IosDevice) error { - for _, device := range devices { - var err error = nil - err = sendQTConfigControlRequest(device) - if err != nil { - return err - } - } - return nil -} - -func sendQTConfigControlRequest(device IosDevice) error { - response := make([]byte, 0) - val, err := device.usbDevice.Control(0x40, 0x52, 0x00, 0x02, response) - - if err != nil { - log.Fatal("Failed sending control transfer for enabling hidden QT config", err) - return err - } - log.Debugf("Enabling QT config RC:%d", val) - return nil -} - -func StartReading(device IosDevice) { - log.Debug("Enabling Quicktime Config for %s", device.SerialNumber) - - config, err := device.enableQuickTimeConfig() - defer func() { - log.Debug("closing Device") - err := config.Close() - if err != nil { - log.Warn("Failed closing device in shutdown", err) - } - log.Debug("re-enabling default device config") - err = device.enableUsbMuxConfig() - if err != nil { - log.Fatal("Failed re-enabling UsbMuxConfig, your device might be broken.", err) - } - }() - if err != nil { - log.Fatal("Failed enabling Quicktime Device Config. Is Quicktime running on your Machine? If so, close it.") - return - } - - log.Info("QT Config is active: %s", config.String()) - - //in idx muss sicher der endpoint rein - duration, _ := time.ParseDuration("20ms") - device.usbDevice.ControlTimeout = duration - val, err := device.usbDevice.Control(0x02, 0x01, 0, 0x86, make([]byte, 0)) - if err != nil { - log.Fatal("failed control", err) - return - } - log.Infof("Got %d as val ", val) - - iface, err := grabQuickTimeInterface(config) - if err != nil { - log.Fatal("Couldnt get Quicktime Interface") - return - } - inEndpoint, err := iface.InEndpoint(grabInBulk(iface.Setting)) - if err != nil { - log.Fatal("couldnt get InEndpoint") - return - } - stream, err := inEndpoint.NewStream(8, 3) - if err != nil { - log.Fatal("couldnt create stream") - return - } - buffer := make([]byte, 70000) - n, err := stream.Read(buffer) - if err != nil { - log.Fatal("coudlnt read bytes") - return - } - log.Info("read %d bytes", n) -} - -func grabInBulk(setting gousb.InterfaceSetting) int { - for _, v := range setting.Endpoints { - if v.Direction == gousb.EndpointDirectionIn { - return v.Number - } - } - //TODO: error - return -1 -} - -func grabQuickTimeInterface(config *gousb.Config) (*gousb.Interface, error) { - _, ifaceIndex := findInterfaceForSubclass(config.Desc, QuicktimeSubclass) - return config.Interface(ifaceIndex, 0) -}