mirror of
https://github.com/stashapp/stash.git
synced 2025-12-15 12:52:38 +01:00
DLNA (#1364)
This commit is contained in:
parent
0f579076b6
commit
76019af3e5
209 changed files with 15361 additions and 22 deletions
1
go.mod
1
go.mod
|
|
@ -3,6 +3,7 @@ module github.com/stashapp/stash
|
|||
require (
|
||||
github.com/99designs/gqlgen v0.12.2
|
||||
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953
|
||||
github.com/anacrolix/dms v1.2.2
|
||||
github.com/antchfx/htmlquery v1.2.3
|
||||
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c
|
||||
github.com/chromedp/chromedp v0.5.3
|
||||
|
|
|
|||
26
go.sum
26
go.sum
|
|
@ -28,6 +28,7 @@ github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF0
|
|||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953 h1:+iPJDL28FxZhEdtJ9qykrMt/oDiOvlzTa0zV06nUcFM=
|
||||
|
|
@ -41,6 +42,13 @@ github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVb
|
|||
github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
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/anacrolix/dms v1.2.2 h1:0mk2/DXNqa5KDDbaLgFPf3oMV6VCGdFNh3d/gt4oafM=
|
||||
github.com/anacrolix/dms v1.2.2/go.mod h1:msPKAoppoNRfrYplJqx63FZ+VipDZ4Xsj3KzIQxyU7k=
|
||||
github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||
github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c=
|
||||
github.com/anacrolix/ffprobe v1.0.0/go.mod h1:BIw+Bjol6CWjm/CRWrVLk2Vy+UYlkgmBZ05vpSYqZPw=
|
||||
github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo=
|
||||
github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
|
|
@ -64,6 +72,8 @@ github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCS
|
|||
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
|
||||
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||
github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g=
|
||||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
||||
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c h1:qM1xzKK8kc93zKPkxK4iqtjksqDDrU6g9wGnr30jyLo=
|
||||
|
|
@ -115,6 +125,8 @@ github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r
|
|||
github.com/docker/docker v0.7.3-0.20190108045446-77df18c24acf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||
github.com/dustin/go-humanize v0.0.0-20180421182945-02af3965c54e/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
|
|
@ -133,6 +145,8 @@ github.com/fvbommel/sortorder v1.0.2 h1:mV4o8B2hKboCdkJm+a7uX/SIpZob4JzUpc5GGnM4
|
|||
github.com/fvbommel/sortorder v1.0.2/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/glycerine/go-unsnap-stream v0.0.0-20180323001048-9f0cb55181dd/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
|
||||
github.com/glycerine/goconvey v0.0.0-20180728074245-46e3a41ad493/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
|
|
@ -384,6 +398,7 @@ github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8l
|
|||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
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=
|
||||
|
|
@ -403,6 +418,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
|
|
@ -453,6 +469,7 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m
|
|||
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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
|
||||
|
|
@ -472,6 +489,7 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
|||
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
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=
|
||||
|
|
@ -569,6 +587,7 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
|||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mongodb/mongo-go-driver v0.3.0/go.mod h1:NK/HWDIIZkaYsnYa0hmtP443T5ELr0KDecmIioVuuyU=
|
||||
github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q=
|
||||
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
|
||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007 h1:Ohgj9L0EYOgXxkDp+bczlMBiulwmqYzQpvQNUdtt3oc=
|
||||
|
|
@ -593,6 +612,7 @@ github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJ
|
|||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
|
|
@ -641,6 +661,7 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
|
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs=
|
||||
|
|
@ -689,6 +710,7 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4
|
|||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
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=
|
||||
|
|
@ -736,6 +758,7 @@ github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0
|
|||
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
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/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
|
|
@ -751,6 +774,7 @@ github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj77
|
|||
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
|
||||
github.com/vektra/mockery/v2 v2.2.1 h1:EYgPvxyYkm/0JKs62qlVc9pO+ljb8biPbDWabk5/PmI=
|
||||
github.com/vektra/mockery/v2 v2.2.1/go.mod h1:rBZUbbhMbiSX1WlCGsOgAi6xjuJGxB7KKbnoL0XNYW8=
|
||||
github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
|
||||
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
|
||||
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
|
||||
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
|
||||
|
|
@ -850,6 +874,7 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r
|
|||
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-20190415214537-1da14a5a36f2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/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=
|
||||
|
|
@ -911,6 +936,7 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
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-20190415145633-3fd5a3612ccd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA=
|
||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
|||
|
|
@ -55,6 +55,13 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||
slideshowDelay
|
||||
}
|
||||
|
||||
fragment ConfigDLNAData on ConfigDLNAResult {
|
||||
serverName
|
||||
enabled
|
||||
whitelistedIPs
|
||||
interfaces
|
||||
}
|
||||
|
||||
fragment ConfigData on ConfigResult {
|
||||
general {
|
||||
...ConfigGeneralData
|
||||
|
|
@ -62,4 +69,7 @@ fragment ConfigData on ConfigResult {
|
|||
interface {
|
||||
...ConfigInterfaceData
|
||||
}
|
||||
dlna {
|
||||
...ConfigDLNAData
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ mutation ConfigureInterface($input: ConfigInterfaceInput!) {
|
|||
}
|
||||
}
|
||||
|
||||
mutation ConfigureDLNA($input: ConfigDLNAInput!) {
|
||||
configureDLNA(input: $input) {
|
||||
...ConfigDLNAData
|
||||
}
|
||||
}
|
||||
|
||||
mutation GenerateAPIKey($input: GenerateAPIKeyInput!) {
|
||||
generateAPIKey(input: $input)
|
||||
}
|
||||
|
|
|
|||
15
graphql/documents/mutations/dlna.graphql
Normal file
15
graphql/documents/mutations/dlna.graphql
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
mutation EnableDLNA($input: EnableDLNAInput!) {
|
||||
enableDLNA(input: $input)
|
||||
}
|
||||
|
||||
mutation DisableDLNA($input: DisableDLNAInput!) {
|
||||
disableDLNA(input: $input)
|
||||
}
|
||||
|
||||
mutation AddTempDLNAIP($input: AddTempDLNAIPInput!) {
|
||||
addTempDLNAIP(input: $input)
|
||||
}
|
||||
|
||||
mutation RemoveTempDLNAIP($input: RemoveTempDLNAIPInput!) {
|
||||
removeTempDLNAIP(input: $input)
|
||||
}
|
||||
11
graphql/documents/queries/dlna.graphql
Normal file
11
graphql/documents/queries/dlna.graphql
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
query DLNAStatus {
|
||||
dlnaStatus {
|
||||
running
|
||||
until
|
||||
recentIPAddresses
|
||||
allowedIPAddresses {
|
||||
ipAddress
|
||||
until
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -110,6 +110,8 @@ type Query {
|
|||
systemStatus: SystemStatus!
|
||||
jobStatus: MetadataUpdateStatus!
|
||||
|
||||
dlnaStatus: DLNAStatus!
|
||||
|
||||
# Get everything
|
||||
|
||||
allPerformers: [Performer!]!
|
||||
|
|
@ -196,6 +198,7 @@ type Mutation {
|
|||
"""Change general configuration options"""
|
||||
configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
|
||||
configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
|
||||
configureDLNA(input: ConfigDLNAInput!): ConfigDLNAResult!
|
||||
|
||||
"""Generate and set (or clear) API key"""
|
||||
generateAPIKey(input: GenerateAPIKeyInput!): String!
|
||||
|
|
@ -238,6 +241,15 @@ type Mutation {
|
|||
|
||||
"""Run batch performer tag task. Returns the job ID."""
|
||||
stashBoxBatchPerformerTag(input: StashBoxBatchPerformerTagInput!): String!
|
||||
|
||||
"""Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default"""
|
||||
enableDLNA(input: EnableDLNAInput!): Boolean!
|
||||
"""Disables DLNA for an optional duration. Has no effect if DLNA is disabled by default"""
|
||||
disableDLNA(input: DisableDLNAInput!): Boolean!
|
||||
"""Enables an IP address for DLNA for an optional duration"""
|
||||
addTempDLNAIP(input: AddTempDLNAIPInput!): Boolean!
|
||||
"""Removes an IP address from the temporary DLNA whitelist"""
|
||||
removeTempDLNAIP(input: RemoveTempDLNAIPInput!): Boolean!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
|
|
|
|||
|
|
@ -216,10 +216,31 @@ type ConfigInterfaceResult {
|
|||
slideshowDelay: Int
|
||||
}
|
||||
|
||||
input ConfigDLNAInput {
|
||||
serverName: String
|
||||
"""True if DLNA service should be enabled by default"""
|
||||
enabled: Boolean
|
||||
"""List of IPs whitelisted for DLNA service"""
|
||||
whitelistedIPs: [String!]
|
||||
"""List of interfaces to run DLNA on. Empty for all"""
|
||||
interfaces: [String!]
|
||||
}
|
||||
|
||||
type ConfigDLNAResult {
|
||||
serverName: String!
|
||||
"""True if DLNA service should be enabled by default"""
|
||||
enabled: Boolean!
|
||||
"""List of IPs whitelisted for DLNA service"""
|
||||
whitelistedIPs: [String!]!
|
||||
"""List of interfaces to run DLNA on. Empty for all"""
|
||||
interfaces: [String!]!
|
||||
}
|
||||
|
||||
"""All configuration settings"""
|
||||
type ConfigResult {
|
||||
general: ConfigGeneralResult!
|
||||
interface: ConfigInterfaceResult!
|
||||
dlna: ConfigDLNAResult!
|
||||
}
|
||||
|
||||
"""Directory structure of a path"""
|
||||
|
|
|
|||
35
graphql/schema/types/dlna.graphql
Normal file
35
graphql/schema/types/dlna.graphql
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
|
||||
|
||||
type DLNAIP {
|
||||
ipAddress: String!
|
||||
"""Time until IP will be no longer allowed/disallowed"""
|
||||
until: Time
|
||||
}
|
||||
|
||||
type DLNAStatus {
|
||||
running: Boolean!
|
||||
"""If not currently running, time until it will be started. If running, time until it will be stopped"""
|
||||
until: Time
|
||||
recentIPAddresses: [String!]!
|
||||
allowedIPAddresses: [DLNAIP!]!
|
||||
}
|
||||
|
||||
input EnableDLNAInput {
|
||||
"""Duration to enable, in minutes. 0 or null for indefinite."""
|
||||
duration: Int
|
||||
}
|
||||
|
||||
input DisableDLNAInput {
|
||||
"""Duration to enable, in minutes. 0 or null for indefinite."""
|
||||
duration: Int
|
||||
}
|
||||
|
||||
input AddTempDLNAIPInput {
|
||||
address: String!
|
||||
"""Duration to enable, in minutes. 0 or null for indefinite."""
|
||||
duration: Int
|
||||
}
|
||||
|
||||
input RemoveTempDLNAIPInput {
|
||||
address: String!
|
||||
}
|
||||
|
|
@ -253,6 +253,37 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
|
|||
return makeConfigInterfaceResult(), nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) ConfigureDlna(ctx context.Context, input models.ConfigDLNAInput) (*models.ConfigDLNAResult, error) {
|
||||
c := config.GetInstance()
|
||||
|
||||
if input.ServerName != nil {
|
||||
c.Set(config.DLNAServerName, *input.ServerName)
|
||||
}
|
||||
|
||||
c.Set(config.DLNADefaultIPWhitelist, input.WhitelistedIPs)
|
||||
|
||||
currentDLNAEnabled := c.GetDLNADefaultEnabled()
|
||||
if input.Enabled != nil && *input.Enabled != currentDLNAEnabled {
|
||||
c.Set(config.DLNADefaultEnabled, *input.Enabled)
|
||||
|
||||
// start/stop the DLNA service as needed
|
||||
dlnaService := manager.GetInstance().DLNAService
|
||||
if !*input.Enabled && dlnaService.IsRunning() {
|
||||
dlnaService.Stop(nil)
|
||||
} else if *input.Enabled && !dlnaService.IsRunning() {
|
||||
dlnaService.Start(nil)
|
||||
}
|
||||
}
|
||||
|
||||
c.Set(config.DLNAInterfaces, input.Interfaces)
|
||||
|
||||
if err := c.Write(); err != nil {
|
||||
return makeConfigDLNAResult(), err
|
||||
}
|
||||
|
||||
return makeConfigDLNAResult(), nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) GenerateAPIKey(ctx context.Context, input models.GenerateAPIKeyInput) (string, error) {
|
||||
c := config.GetInstance()
|
||||
|
||||
|
|
|
|||
42
pkg/api/resolver_mutation_dlna.go
Normal file
42
pkg/api/resolver_mutation_dlna.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/manager"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func (r *mutationResolver) EnableDlna(ctx context.Context, input models.EnableDLNAInput) (bool, error) {
|
||||
err := manager.GetInstance().DLNAService.Start(parseMinutes(input.Duration))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) DisableDlna(ctx context.Context, input models.DisableDLNAInput) (bool, error) {
|
||||
manager.GetInstance().DLNAService.Stop(parseMinutes(input.Duration))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AddTempDlnaip(ctx context.Context, input models.AddTempDLNAIPInput) (bool, error) {
|
||||
manager.GetInstance().DLNAService.AddTempDLNAIP(input.Address, parseMinutes(input.Duration))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveTempDlnaip(ctx context.Context, input models.RemoveTempDLNAIPInput) (bool, error) {
|
||||
ret := manager.GetInstance().DLNAService.RemoveTempDLNAIP(input.Address)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func parseMinutes(minutes *int) *time.Duration {
|
||||
var ret *time.Duration
|
||||
if minutes != nil {
|
||||
d := time.Duration(*minutes) * time.Minute
|
||||
ret = &d
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ func makeConfigResult() *models.ConfigResult {
|
|||
return &models.ConfigResult{
|
||||
General: makeConfigGeneralResult(),
|
||||
Interface: makeConfigInterfaceResult(),
|
||||
Dlna: makeConfigDLNAResult(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,3 +110,14 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
|
|||
SlideshowDelay: &slideshowDelay,
|
||||
}
|
||||
}
|
||||
|
||||
func makeConfigDLNAResult() *models.ConfigDLNAResult {
|
||||
config := config.GetInstance()
|
||||
|
||||
return &models.ConfigDLNAResult{
|
||||
ServerName: config.GetDLNAServerName(),
|
||||
Enabled: config.GetDLNADefaultEnabled(),
|
||||
WhitelistedIPs: config.GetDLNADefaultIPWhitelist(),
|
||||
Interfaces: config.GetDLNAInterfaces(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
pkg/api/resolver_query_dlna.go
Normal file
12
pkg/api/resolver_query_dlna.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/stashapp/stash/pkg/manager"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
func (r *queryResolver) DlnaStatus(ctx context.Context) (*models.DLNAStatus, error) {
|
||||
return manager.GetInstance().DLNAService.Status(), nil
|
||||
}
|
||||
|
|
@ -16,7 +16,8 @@ import (
|
|||
)
|
||||
|
||||
type sceneRoutes struct {
|
||||
txnManager models.TransactionManager
|
||||
txnManager models.TransactionManager
|
||||
sceneServer manager.SceneServer
|
||||
}
|
||||
|
||||
func (rs sceneRoutes) Routes() chi.Router {
|
||||
|
|
@ -69,12 +70,11 @@ func getSceneFileContainer(scene *models.Scene) ffmpeg.Container {
|
|||
|
||||
func (rs sceneRoutes) StreamDirect(w http.ResponseWriter, r *http.Request) {
|
||||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm()
|
||||
|
||||
filepath := manager.GetInstance().Paths.Scene.GetStreamPath(scene.Path, scene.GetHash(fileNamingAlgo))
|
||||
manager.RegisterStream(filepath, &w)
|
||||
http.ServeFile(w, r, filepath)
|
||||
manager.WaitAndDeregisterStream(filepath, &w, r)
|
||||
ss := manager.SceneServer{
|
||||
TXNManager: rs.txnManager,
|
||||
}
|
||||
ss.StreamSceneDirect(scene, w, r)
|
||||
}
|
||||
|
||||
func (rs sceneRoutes) StreamMKV(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -178,20 +178,11 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, vi
|
|||
|
||||
func (rs sceneRoutes) Screenshot(w http.ResponseWriter, r *http.Request) {
|
||||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
filepath := manager.GetInstance().Paths.Scene.GetScreenshotPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()))
|
||||
|
||||
// fall back to the scene image blob if the file isn't present
|
||||
screenshotExists, _ := utils.FileExists(filepath)
|
||||
if screenshotExists {
|
||||
http.ServeFile(w, r, filepath)
|
||||
} else {
|
||||
var cover []byte
|
||||
rs.txnManager.WithReadTxn(r.Context(), func(repo models.ReaderRepository) error {
|
||||
cover, _ = repo.Scene().GetCover(scene.ID)
|
||||
return nil
|
||||
})
|
||||
utils.ServeImage(cover, w, r)
|
||||
ss := manager.SceneServer{
|
||||
TXNManager: rs.txnManager,
|
||||
}
|
||||
ss.ServeScreenshot(scene, w, r)
|
||||
}
|
||||
|
||||
func (rs sceneRoutes) Preview(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ func Start() {
|
|||
printLatestVersion()
|
||||
logger.Infof("stash is listening on " + address)
|
||||
logger.Infof("stash is running at https://" + displayAddress + "/")
|
||||
logger.Fatal(httpsServer.ListenAndServeTLS("", ""))
|
||||
logger.Error(httpsServer.ListenAndServeTLS("", ""))
|
||||
}()
|
||||
} else {
|
||||
server := &http.Server{
|
||||
|
|
@ -293,7 +293,7 @@ func Start() {
|
|||
printLatestVersion()
|
||||
logger.Infof("stash is listening on " + address)
|
||||
logger.Infof("stash is running at http://" + displayAddress + "/")
|
||||
logger.Fatal(server.ListenAndServe())
|
||||
logger.Error(server.ListenAndServe())
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
477
pkg/dlna/cd-service-desc.go
Normal file
477
pkg/dlna/cd-service-desc.go
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
package dlna
|
||||
|
||||
// From: https://github.com/anacrolix/dms
|
||||
// Copyright (c) 2012, Matt Joiner <anacrolix@gmail.com>.
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the <organization> nor the
|
||||
// names of its contributors may be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
const contentDirectoryServiceDescription = `<?xml version="1.0"?>
|
||||
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<actionList>
|
||||
<action>
|
||||
<name>GetSearchCapabilities</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>SearchCaps</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>SearchCapabilities</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetSortCapabilities</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>SortCaps</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>SortCapabilities</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetSortExtensionCapabilities</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>SortExtensionCaps</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>SortExtensionCapabilities</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetFeatureList</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>FeatureList</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>FeatureList</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetSystemUpdateID</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>Id</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>SystemUpdateID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>Browse</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ObjectID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>BrowseFlag</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Filter</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>StartingIndex</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>RequestedCount</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>SortCriteria</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Result</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>NumberReturned</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TotalMatches</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>UpdateID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>Search</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ContainerID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>SearchCriteria</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_SearchCriteria</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Filter</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>StartingIndex</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>RequestedCount</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>SortCriteria</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Result</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>NumberReturned</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TotalMatches</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>UpdateID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>CreateObject</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ContainerID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Elements</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>ObjectID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Result</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>DestroyObject</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ObjectID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>UpdateObject</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ObjectID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>CurrentTagValue</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TagValueList</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>NewTagValue</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TagValueList</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>MoveObject</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ObjectID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>NewParentID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>NewObjectID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>ImportResource</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>SourceURI</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>DestinationURI</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TransferID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>ExportResource</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>SourceURI</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>DestinationURI</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TransferID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>StopTransferResource</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>TransferID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>DeleteResource</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ResourceURI</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_URI</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetTransferProgress</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>TransferID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TransferStatus</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferStatus</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TransferLength</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferLength</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>TransferTotal</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_TransferTotal</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>CreateReference</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ContainerID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>ObjectID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>NewID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
</actionList>
|
||||
<serviceStateTable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>SearchCapabilities</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>SortCapabilities</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>SortExtensionCapabilities</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>SystemUpdateID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>ContainerUpdateIDs</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>TransferIDs</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>FeatureList</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_ObjectID</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_Result</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_SearchCriteria</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_BrowseFlag</name>
|
||||
<dataType>string</dataType>
|
||||
<allowedValueList>
|
||||
<allowedValue>BrowseMetadata</allowedValue>
|
||||
<allowedValue>BrowseDirectChildren</allowedValue>
|
||||
</allowedValueList>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_Filter</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_SortCriteria</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_Index</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_Count</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_UpdateID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_TransferID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_TransferStatus</name>
|
||||
<dataType>string</dataType>
|
||||
<allowedValueList>
|
||||
<allowedValue>COMPLETED</allowedValue>
|
||||
<allowedValue>ERROR</allowedValue>
|
||||
<allowedValue>IN_PROGRESS</allowedValue>
|
||||
<allowedValue>STOPPED</allowedValue>
|
||||
</allowedValueList>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_TransferLength</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_TransferTotal</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_TagValueList</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_URI</name>
|
||||
<dataType>uri</dataType>
|
||||
</stateVariable>
|
||||
</serviceStateTable>
|
||||
</scpd>`
|
||||
706
pkg/dlna/cds.go
Normal file
706
pkg/dlna/cds.go
Normal file
|
|
@ -0,0 +1,706 @@
|
|||
package dlna
|
||||
|
||||
// from https://github.com/rclone/rclone
|
||||
// Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/
|
||||
|
||||
// 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.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/dms/dlna"
|
||||
"github.com/anacrolix/dms/upnp"
|
||||
"github.com/anacrolix/dms/upnpav"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
var pageSize = 100
|
||||
|
||||
type browse struct {
|
||||
ObjectID string
|
||||
BrowseFlag string
|
||||
Filter string
|
||||
StartingIndex int
|
||||
RequestedCount int
|
||||
}
|
||||
|
||||
type contentDirectoryService struct {
|
||||
*Server
|
||||
upnp.Eventing
|
||||
txnManager models.TransactionManager
|
||||
}
|
||||
|
||||
func formatDurationSexagesimal(d time.Duration) string {
|
||||
ns := d % time.Second
|
||||
d /= time.Second
|
||||
s := d % 60
|
||||
d /= 60
|
||||
m := d % 60
|
||||
d /= 60
|
||||
h := d
|
||||
ret := fmt.Sprintf("%d:%02d:%02d.%09d", h, m, s, ns)
|
||||
ret = strings.TrimRight(ret, "0")
|
||||
ret = strings.TrimRight(ret, ".")
|
||||
return ret
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) updateIDString() string {
|
||||
return fmt.Sprintf("%d", uint32(os.Getpid()))
|
||||
}
|
||||
|
||||
func sceneToContainer(scene *models.Scene, parent string, host string) interface{} {
|
||||
// make stash server URL
|
||||
// TODO - fix this
|
||||
iconURI := (&url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
Path: iconPath,
|
||||
RawQuery: url.Values{
|
||||
"scene": {strconv.Itoa(scene.ID)},
|
||||
"c": {"jpeg"},
|
||||
}.Encode(),
|
||||
}).String()
|
||||
|
||||
// Object goes first
|
||||
obj := upnpav.Object{
|
||||
ID: strconv.Itoa(scene.ID),
|
||||
Restricted: 1,
|
||||
ParentID: parent,
|
||||
Title: scene.GetTitle(),
|
||||
Class: "object.item.videoItem",
|
||||
Icon: iconURI,
|
||||
AlbumArtURI: iconURI,
|
||||
}
|
||||
|
||||
// Wrap up
|
||||
item := upnpav.Item{
|
||||
Object: obj,
|
||||
Res: make([]upnpav.Resource, 0, 1),
|
||||
}
|
||||
|
||||
mimeType := "video/mp4"
|
||||
size, _ := strconv.Atoi(scene.Size.String)
|
||||
|
||||
duration := int64(scene.Duration.Float64)
|
||||
|
||||
item.Res = append(item.Res, upnpav.Resource{
|
||||
URL: (&url.URL{
|
||||
Scheme: "http",
|
||||
Host: host,
|
||||
Path: resPath,
|
||||
RawQuery: url.Values{
|
||||
"scene": {strconv.Itoa(scene.ID)},
|
||||
}.Encode(),
|
||||
}).String(),
|
||||
ProtocolInfo: fmt.Sprintf("http-get:*:%s:%s", mimeType, dlna.ContentFeatures{
|
||||
SupportRange: true,
|
||||
}.String()),
|
||||
Bitrate: uint(scene.Bitrate.Int64),
|
||||
// TODO - make %d:%02d:%02d string
|
||||
Duration: formatDurationSexagesimal(time.Duration(duration) * time.Second),
|
||||
Size: uint64(size),
|
||||
// Resolution: resolution,
|
||||
})
|
||||
|
||||
item.Res = append(item.Res, upnpav.Resource{
|
||||
URL: iconURI,
|
||||
ProtocolInfo: "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_MED",
|
||||
})
|
||||
|
||||
return item
|
||||
}
|
||||
|
||||
// ContentDirectory object from ObjectID.
|
||||
func (me *contentDirectoryService) objectFromID(id string) (o object, err error) {
|
||||
o.Path, err = url.QueryUnescape(id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if o.Path == "0" {
|
||||
o.Path = "/"
|
||||
}
|
||||
// o.Path = path.Clean(o.Path)
|
||||
// if !path.IsAbs(o.Path) {
|
||||
// err = fmt.Errorf("bad ObjectID %v", o.Path)
|
||||
// return
|
||||
// }
|
||||
o.RootObjectPath = me.RootObjectPath
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func childPath(paths []string) []string {
|
||||
if len(paths) > 1 {
|
||||
return paths[1:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http.Request) (map[string]string, error) {
|
||||
host := r.Host
|
||||
// userAgent := r.UserAgent()
|
||||
switch action {
|
||||
case "GetSystemUpdateID":
|
||||
return map[string]string{
|
||||
"Id": me.updateIDString(),
|
||||
}, nil
|
||||
case "GetSortCapabilities":
|
||||
return map[string]string{
|
||||
"SortCaps": "dc:title",
|
||||
}, nil
|
||||
case "Browse":
|
||||
var browse browse
|
||||
if err := xml.Unmarshal([]byte(argsXML), &browse); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
obj, err := me.objectFromID(browse.ObjectID)
|
||||
if err != nil {
|
||||
return nil, upnp.Errorf(upnpav.NoSuchObjectErrorCode, err.Error())
|
||||
}
|
||||
|
||||
switch browse.BrowseFlag {
|
||||
case "BrowseDirectChildren":
|
||||
// Read folder and return children
|
||||
// TODO: check if obj == 0 and return root objects
|
||||
// TODO: check if special path and return files
|
||||
|
||||
var objs []interface{}
|
||||
|
||||
if obj.IsRoot() {
|
||||
objs = getRootObjects()
|
||||
}
|
||||
|
||||
paths := strings.Split(obj.Path, "/")
|
||||
|
||||
// All videos
|
||||
if obj.Path == "all" {
|
||||
objs = me.getAllScenes(host)
|
||||
}
|
||||
|
||||
if strings.HasPrefix(obj.Path, "all/") {
|
||||
page := getPageFromID(paths)
|
||||
if page != nil {
|
||||
objs = me.getPageVideos(&models.SceneFilterType{}, "all", *page, host)
|
||||
}
|
||||
}
|
||||
|
||||
// Saved searches
|
||||
// if obj.Path == "saved-searches" {
|
||||
// var savedPlaylists []models.Playlist
|
||||
// db, _ := models.GetDB()
|
||||
// db.Where("is_deo_enabled = ?", true).Order("ordering asc").Find(&savedPlaylists)
|
||||
// db.Close()
|
||||
|
||||
// for _, playlist := range savedPlaylists {
|
||||
// objs = append(objs, upnpav.Container{Object: upnpav.Object{
|
||||
// ID: "saved-searches/" + strconv.Itoa(int(playlist.ID)),
|
||||
// Restricted: 1,
|
||||
// ParentID: "saved-searches",
|
||||
// Class: "object.container.storageFolder",
|
||||
// Title: playlist.Name,
|
||||
// }})
|
||||
// }
|
||||
// }
|
||||
|
||||
// if strings.HasPrefix(obj.Path, "saved-searches/") {
|
||||
// id := strings.Split(obj.Path, "/")
|
||||
|
||||
// var savedPlaylist models.Playlist
|
||||
// db, _ := models.GetDB()
|
||||
// db.Where("id = ?", id[1]).First(&savedPlaylist)
|
||||
// db.Close()
|
||||
|
||||
// var r models.RequestSceneList
|
||||
// if err := json.Unmarshal([]byte(savedPlaylist.SearchParams), &r); err == nil {
|
||||
// r.IsAccessible = optional.NewBool(true)
|
||||
// r.IsAvailable = optional.NewBool(true)
|
||||
// data := models.QueryScenesFull(r)
|
||||
|
||||
// for i := range data.Scenes {
|
||||
// objs = append(objs, me.sceneToContainer(data.Scenes[i], "sites/"+id[1], host))
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Studios
|
||||
if obj.Path == "studios" {
|
||||
objs = me.getStudios()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(obj.Path, "studios/") {
|
||||
objs = me.getStudioScenes(childPath(paths), host)
|
||||
}
|
||||
|
||||
// Tags
|
||||
if obj.Path == "tags" {
|
||||
objs = me.getTags()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(obj.Path, "tags/") {
|
||||
objs = me.getTagScenes(childPath(paths), host)
|
||||
}
|
||||
|
||||
// Performers
|
||||
if obj.Path == "performers" {
|
||||
objs = me.getPerformers()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(obj.Path, "performers/") {
|
||||
objs = me.getPerformerScenes(childPath(paths), host)
|
||||
}
|
||||
|
||||
// Movies
|
||||
if obj.Path == "movies" {
|
||||
objs = me.getMovies()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(obj.Path, "movies/") {
|
||||
objs = me.getMovieScenes(childPath(paths), host)
|
||||
}
|
||||
|
||||
// Rating
|
||||
if obj.Path == "rating" {
|
||||
objs = me.getRating()
|
||||
}
|
||||
|
||||
if strings.HasPrefix(obj.Path, "rating/") {
|
||||
objs = me.getRatingScenes(childPath(paths), host)
|
||||
}
|
||||
|
||||
result, err := xml.Marshal(objs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"TotalMatches": fmt.Sprint(len(objs)),
|
||||
"NumberReturned": fmt.Sprint(len(objs)),
|
||||
"Result": didl_lite(string(result)),
|
||||
"UpdateID": me.updateIDString(),
|
||||
}, nil
|
||||
case "BrowseMetadata":
|
||||
var scene *models.Scene
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
sceneID, err := strconv.Atoi(obj.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scene, err = r.Scene().Find(sceneID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
if scene != nil {
|
||||
upnpObject := sceneToContainer(scene, "-1", host)
|
||||
result, err := xml.Marshal(upnpObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// http://upnp.org/specs/av/UPnP-av-ContentDirectory-v1-Service.pdf
|
||||
// maximum update ID is 2**32, then rolls back to 0
|
||||
const maxUpdateID int64 = 1 << 32
|
||||
updateID := scene.UpdatedAt.Timestamp.Unix() % maxUpdateID
|
||||
return map[string]string{
|
||||
"Result": didl_lite(string(result)),
|
||||
"NumberReturned": "1",
|
||||
"TotalMatches": "1",
|
||||
"UpdateID": fmt.Sprint(updateID),
|
||||
}, nil
|
||||
} else {
|
||||
return nil, upnp.Errorf(upnpav.NoSuchObjectErrorCode, "scene not found")
|
||||
}
|
||||
default:
|
||||
return nil, upnp.Errorf(upnp.ArgumentValueInvalidErrorCode, "unhandled browse flag: %v", browse.BrowseFlag)
|
||||
}
|
||||
case "GetSearchCapabilities":
|
||||
return map[string]string{
|
||||
"SearchCaps": "",
|
||||
}, nil
|
||||
// from https://github.com/rclone/rclone/blob/master/cmd/serve/dlna/cds.go
|
||||
// Samsung Extensions
|
||||
case "X_GetFeatureList":
|
||||
return map[string]string{
|
||||
"FeatureList": `<Features xmlns="urn:schemas-upnp-org:av:avs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:schemas-upnp-org:av:avs http://www.upnp.org/schemas/av/avs.xsd">
|
||||
<Feature name="samsung.com_BASICVIEW" version="1">
|
||||
<container id="0" type="object.item.imageItem"/>
|
||||
<container id="0" type="object.item.audioItem"/>
|
||||
<container id="0" type="object.item.videoItem"/>
|
||||
</Feature>
|
||||
</Features>`}, nil
|
||||
case "X_SetBookmark":
|
||||
// just ignore
|
||||
return map[string]string{}, nil
|
||||
default:
|
||||
return nil, upnp.InvalidActionError
|
||||
}
|
||||
}
|
||||
|
||||
func makeStorageFolder(id, title, parentID string) upnpav.Container {
|
||||
defaultChildCount := 1
|
||||
return upnpav.Container{
|
||||
Object: upnpav.Object{
|
||||
ID: id,
|
||||
Restricted: 1,
|
||||
ParentID: parentID,
|
||||
Class: "object.container.storageFolder",
|
||||
Title: title,
|
||||
},
|
||||
ChildCount: defaultChildCount,
|
||||
}
|
||||
}
|
||||
|
||||
func getRootObjects() []interface{} {
|
||||
const rootID = "0"
|
||||
|
||||
var objs []interface{}
|
||||
|
||||
objs = append(objs, makeStorageFolder("all", "all", rootID))
|
||||
objs = append(objs, makeStorageFolder("performers", "performers", rootID))
|
||||
objs = append(objs, makeStorageFolder("tags", "tags", rootID))
|
||||
objs = append(objs, makeStorageFolder("studios", "studios", rootID))
|
||||
objs = append(objs, makeStorageFolder("movies", "movies", rootID))
|
||||
objs = append(objs, makeStorageFolder("rating", "rating", rootID))
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getVideos(sceneFilter *models.SceneFilterType, parentID string, host string) []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
sort := "title"
|
||||
findFilter := &models.FindFilterType{
|
||||
PerPage: &pageSize,
|
||||
Sort: &sort,
|
||||
}
|
||||
|
||||
scenes, total, err := r.Scene().Query(sceneFilter, findFilter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if total > pageSize {
|
||||
pager := scenePager{
|
||||
sceneFilter: sceneFilter,
|
||||
parentID: parentID,
|
||||
}
|
||||
|
||||
objs, err = pager.getPages(r, total)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
for _, s := range scenes {
|
||||
objs = append(objs, sceneToContainer(s, parentID, host))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getPageVideos(sceneFilter *models.SceneFilterType, parentID string, page int, host string) []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
pager := scenePager{
|
||||
sceneFilter: sceneFilter,
|
||||
parentID: parentID,
|
||||
}
|
||||
|
||||
var err error
|
||||
objs, err = pager.getPageVideos(r, page, host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func getPageFromID(paths []string) *int {
|
||||
i := utils.StrIndex(paths, "page")
|
||||
if i == -1 || i+1 >= len(paths) {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret, err := strconv.Atoi(paths[i+1])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &ret
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getAllScenes(host string) []interface{} {
|
||||
return me.getVideos(&models.SceneFilterType{}, "all", host)
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getStudios() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
studios, err := r.Studio().All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range studios {
|
||||
objs = append(objs, makeStorageFolder("studios/"+strconv.Itoa(s.ID), s.Name.String, "studios"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getStudioScenes(paths []string, host string) []interface{} {
|
||||
sceneFilter := &models.SceneFilterType{
|
||||
Studios: &models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
Value: []string{paths[0]},
|
||||
},
|
||||
}
|
||||
|
||||
parentID := "studios/" + strings.Join(paths, "/")
|
||||
|
||||
page := getPageFromID(paths)
|
||||
if page != nil {
|
||||
return me.getPageVideos(sceneFilter, parentID, *page, host)
|
||||
}
|
||||
|
||||
return me.getVideos(sceneFilter, parentID, host)
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getTags() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
tags, err := r.Tag().All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range tags {
|
||||
objs = append(objs, makeStorageFolder("tags/"+strconv.Itoa(s.ID), s.Name, "tags"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getTagScenes(paths []string, host string) []interface{} {
|
||||
sceneFilter := &models.SceneFilterType{
|
||||
Tags: &models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
Value: []string{paths[0]},
|
||||
},
|
||||
}
|
||||
|
||||
parentID := "tags/" + strings.Join(paths, "/")
|
||||
|
||||
page := getPageFromID(paths)
|
||||
if page != nil {
|
||||
return me.getPageVideos(sceneFilter, parentID, *page, host)
|
||||
}
|
||||
|
||||
return me.getVideos(sceneFilter, parentID, host)
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getPerformers() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
performers, err := r.Performer().All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range performers {
|
||||
objs = append(objs, makeStorageFolder("performers/"+strconv.Itoa(s.ID), s.Name.String, "performers"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getPerformerScenes(paths []string, host string) []interface{} {
|
||||
sceneFilter := &models.SceneFilterType{
|
||||
Performers: &models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
Value: []string{paths[0]},
|
||||
},
|
||||
}
|
||||
|
||||
parentID := "performers/" + strings.Join(paths, "/")
|
||||
|
||||
page := getPageFromID(paths)
|
||||
if page != nil {
|
||||
return me.getPageVideos(sceneFilter, parentID, *page, host)
|
||||
}
|
||||
|
||||
return me.getVideos(sceneFilter, parentID, host)
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getMovies() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
if err := me.txnManager.WithReadTxn(context.TODO(), func(r models.ReaderRepository) error {
|
||||
movies, err := r.Movie().All()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range movies {
|
||||
objs = append(objs, makeStorageFolder("movies/"+strconv.Itoa(s.ID), s.Name.String, "movies"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getMovieScenes(paths []string, host string) []interface{} {
|
||||
sceneFilter := &models.SceneFilterType{
|
||||
Movies: &models.MultiCriterionInput{
|
||||
Modifier: models.CriterionModifierIncludes,
|
||||
Value: []string{paths[0]},
|
||||
},
|
||||
}
|
||||
|
||||
parentID := "movies/" + strings.Join(paths, "/")
|
||||
|
||||
page := getPageFromID(paths)
|
||||
if page != nil {
|
||||
return me.getPageVideos(sceneFilter, parentID, *page, host)
|
||||
}
|
||||
|
||||
return me.getVideos(sceneFilter, parentID, host)
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getRating() []interface{} {
|
||||
var objs []interface{}
|
||||
|
||||
for r := 1; r <= 5; r++ {
|
||||
rStr := strconv.Itoa(r)
|
||||
objs = append(objs, makeStorageFolder("rating/"+rStr, rStr, "rating"))
|
||||
}
|
||||
|
||||
return objs
|
||||
}
|
||||
|
||||
func (me *contentDirectoryService) getRatingScenes(paths []string, host string) []interface{} {
|
||||
r, err := strconv.Atoi(paths[0])
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sceneFilter := &models.SceneFilterType{
|
||||
Rating: &models.IntCriterionInput{
|
||||
Modifier: models.CriterionModifierEquals,
|
||||
Value: r,
|
||||
},
|
||||
}
|
||||
|
||||
parentID := "rating/" + strings.Join(paths, "/")
|
||||
|
||||
page := getPageFromID(paths)
|
||||
if page != nil {
|
||||
return me.getPageVideos(sceneFilter, parentID, *page, host)
|
||||
}
|
||||
|
||||
return me.getVideos(sceneFilter, parentID, host)
|
||||
}
|
||||
|
||||
// Represents a ContentDirectory object.
|
||||
type object struct {
|
||||
Path string // The cleaned, absolute path for the object relative to the server.
|
||||
RootObjectPath string
|
||||
}
|
||||
|
||||
// Returns the actual local filesystem path for the object.
|
||||
func (o *object) FilePath() string {
|
||||
return filepath.Join(o.RootObjectPath, filepath.FromSlash(o.Path))
|
||||
}
|
||||
|
||||
// Returns the ObjectID for the object. This is used in various ContentDirectory actions.
|
||||
func (o object) ID() string {
|
||||
if len(o.Path) == 1 {
|
||||
return "0"
|
||||
}
|
||||
return url.QueryEscape(o.Path)
|
||||
}
|
||||
|
||||
func (o *object) IsRoot() bool {
|
||||
return o.Path == "/"
|
||||
}
|
||||
|
||||
// Returns the object's parent ObjectID. Fortunately it can be deduced from the
|
||||
// ObjectID (for now).
|
||||
func (o object) ParentID() string {
|
||||
if o.IsRoot() {
|
||||
return "-1"
|
||||
}
|
||||
o.Path = path.Dir(o.Path)
|
||||
return o.ID()
|
||||
}
|
||||
54
pkg/dlna/cds_test.go
Normal file
54
pkg/dlna/cds_test.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package dlna
|
||||
|
||||
// From: https://github.com/anacrolix/dms
|
||||
// Copyright (c) 2012, Matt Joiner <anacrolix@gmail.com>.
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the <organization> nor the
|
||||
// names of its contributors may be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEscapeObjectID(t *testing.T) {
|
||||
o := object{
|
||||
Path: "/some/file",
|
||||
}
|
||||
id := o.ID()
|
||||
if strings.ContainsAny(id, "/") {
|
||||
t.Skip("may not work with some players: object IDs contain '/'")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootObjectID(t *testing.T) {
|
||||
if (object{Path: "/"}).ID() != "0" {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func TestRootParentObjectID(t *testing.T) {
|
||||
if (object{Path: "/"}).ParentID() != "-1" {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
205
pkg/dlna/cm-service-desc.go
Normal file
205
pkg/dlna/cm-service-desc.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package dlna
|
||||
|
||||
// from https://github.com/rclone/rclone
|
||||
// Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/
|
||||
|
||||
// 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.
|
||||
|
||||
const connectionManagerServiceDescription = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<actionList>
|
||||
<action>
|
||||
<name>GetProtocolInfo</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>Source</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>SourceProtocolInfo</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Sink</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>SinkProtocolInfo</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>PrepareForConnection</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>RemoteProtocolInfo</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>PeerConnectionManager</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>PeerConnectionID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Direction</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>ConnectionID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>AVTransportID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>RcsID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>ConnectionComplete</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ConnectionID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetCurrentConnectionIDs</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ConnectionIDs</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>CurrentConnectionIDs</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>GetCurrentConnectionInfo</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>ConnectionID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>RcsID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>AVTransportID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>ProtocolInfo</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>PeerConnectionManager</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>PeerConnectionID</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Direction</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Status</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
</actionList>
|
||||
<serviceStateTable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>SourceProtocolInfo</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>SinkProtocolInfo</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>CurrentConnectionIDs</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_ConnectionStatus</name>
|
||||
<dataType>string</dataType>
|
||||
<allowedValueList>
|
||||
<allowedValue>OK</allowedValue>
|
||||
<allowedValue>ContentFormatMismatch</allowedValue>
|
||||
<allowedValue>InsufficientBandwidth</allowedValue>
|
||||
<allowedValue>UnreliableChannel</allowedValue>
|
||||
<allowedValue>Unknown</allowedValue>
|
||||
</allowedValueList>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_ConnectionManager</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_Direction</name>
|
||||
<dataType>string</dataType>
|
||||
<allowedValueList>
|
||||
<allowedValue>Input</allowedValue>
|
||||
<allowedValue>Output</allowedValue>
|
||||
</allowedValueList>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_ProtocolInfo</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_ConnectionID</name>
|
||||
<dataType>i4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_AVTransportID</name>
|
||||
<dataType>i4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_RcsID</name>
|
||||
<dataType>i4</dataType>
|
||||
</stateVariable>
|
||||
</serviceStateTable>
|
||||
</scpd>`
|
||||
47
pkg/dlna/cms.go
Normal file
47
pkg/dlna/cms.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package dlna
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/anacrolix/dms/upnp"
|
||||
)
|
||||
|
||||
// from https://github.com/rclone/rclone
|
||||
// Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/
|
||||
|
||||
// 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.
|
||||
|
||||
const defaultProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*"
|
||||
|
||||
type connectionManagerService struct {
|
||||
*Server
|
||||
upnp.Eventing
|
||||
}
|
||||
|
||||
func (cms *connectionManagerService) Handle(action string, argsXML []byte, r *http.Request) (map[string]string, error) {
|
||||
switch action {
|
||||
case "GetProtocolInfo":
|
||||
return map[string]string{
|
||||
"Source": defaultProtocolInfo,
|
||||
"Sink": "",
|
||||
}, nil
|
||||
default:
|
||||
return nil, upnp.InvalidActionError
|
||||
}
|
||||
}
|
||||
699
pkg/dlna/dms.go
Normal file
699
pkg/dlna/dms.go
Normal file
|
|
@ -0,0 +1,699 @@
|
|||
package dlna
|
||||
|
||||
// Derived from: https://github.com/anacrolix/dms
|
||||
// Copyright (c) 2012, Matt Joiner <anacrolix@gmail.com>.
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the <organization> nor the
|
||||
// names of its contributors may be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/anacrolix/dms/soap"
|
||||
"github.com/anacrolix/dms/ssdp"
|
||||
"github.com/anacrolix/dms/upnp"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
const (
|
||||
serverField = "Linux/3.4 DLNADOC/1.50 UPnP/1.0 DMS/1.0"
|
||||
rootDeviceType = "urn:schemas-upnp-org:device:MediaServer:1"
|
||||
rootDeviceModelName = "dms 1.0xb"
|
||||
resPath = "/res"
|
||||
iconPath = "/icon"
|
||||
rootDescPath = "/rootDesc.xml"
|
||||
contentDirectorySCPDURL = "/scpd/ContentDirectory.xml"
|
||||
contentDirectoryEventSubURL = "/evt/ContentDirectory"
|
||||
serviceControlURL = "/ctl"
|
||||
deviceIconPath = "/deviceIcon"
|
||||
)
|
||||
|
||||
func makeDeviceUuid(unique string) string {
|
||||
h := md5.New()
|
||||
if _, err := io.WriteString(h, unique); err != nil {
|
||||
panic("makeDeviceUuid write failed: " + err.Error())
|
||||
}
|
||||
buf := h.Sum(nil)
|
||||
return upnp.FormatUUID(buf)
|
||||
}
|
||||
|
||||
// Groups the service definition with its XML description.
|
||||
type service struct {
|
||||
upnp.Service
|
||||
SCPD string
|
||||
}
|
||||
|
||||
// Exposed UPnP AV services.
|
||||
var services = []*service{
|
||||
{
|
||||
Service: upnp.Service{
|
||||
ServiceType: "urn:schemas-upnp-org:service:ContentDirectory:1",
|
||||
ServiceId: "urn:upnp-org:serviceId:ContentDirectory",
|
||||
EventSubURL: contentDirectoryEventSubURL,
|
||||
ControlURL: serviceControlURL,
|
||||
},
|
||||
SCPD: contentDirectoryServiceDescription,
|
||||
},
|
||||
{
|
||||
Service: upnp.Service{
|
||||
ServiceType: "urn:schemas-upnp-org:service:ConnectionManager:1",
|
||||
ServiceId: "urn:upnp-org:serviceId:ConnectionManager",
|
||||
ControlURL: serviceControlURL,
|
||||
},
|
||||
SCPD: connectionManagerServiceDescription,
|
||||
},
|
||||
{
|
||||
Service: upnp.Service{
|
||||
ServiceType: "urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1",
|
||||
ServiceId: "urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar",
|
||||
ControlURL: serviceControlURL,
|
||||
},
|
||||
SCPD: xmsMediaReceiverServiceDescription,
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, s := range services {
|
||||
p := path.Join("/scpd", s.ServiceId)
|
||||
s.SCPDURL = p
|
||||
}
|
||||
}
|
||||
|
||||
func devices() []string {
|
||||
return []string{
|
||||
"urn:schemas-upnp-org:device:MediaServer:1",
|
||||
}
|
||||
}
|
||||
|
||||
func serviceTypes() (ret []string) {
|
||||
for _, s := range services {
|
||||
ret = append(ret, s.ServiceType)
|
||||
}
|
||||
return
|
||||
}
|
||||
func (me *Server) httpPort() int {
|
||||
return me.HTTPConn.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func (me *Server) serveHTTP() error {
|
||||
srv := &http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if me.LogHeaders {
|
||||
logger.Debugf("%s %s", r.Method, r.RequestURI)
|
||||
for k, v := range r.Header {
|
||||
logger.Debugf("%s: %s", k, v)
|
||||
}
|
||||
}
|
||||
w.Header().Set("Ext", "")
|
||||
w.Header().Set("Server", serverField)
|
||||
me.httpServeMux.ServeHTTP(&mitmRespWriter{
|
||||
ResponseWriter: w,
|
||||
logHeader: me.LogHeaders,
|
||||
}, r)
|
||||
}),
|
||||
}
|
||||
err := srv.Serve(me.HTTPConn)
|
||||
select {
|
||||
case <-me.closed:
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// An interface with these flags should be valid for SSDP.
|
||||
const ssdpInterfaceFlags = net.FlagUp | net.FlagMulticast
|
||||
|
||||
func (me *Server) doSSDP() {
|
||||
active := 0
|
||||
stopped := make(chan struct{})
|
||||
for _, if_ := range me.Interfaces {
|
||||
active++
|
||||
go func(if_ net.Interface) {
|
||||
defer func() {
|
||||
stopped <- struct{}{}
|
||||
}()
|
||||
me.ssdpInterface(if_)
|
||||
}(if_)
|
||||
}
|
||||
for active > 0 {
|
||||
<-stopped
|
||||
active--
|
||||
}
|
||||
}
|
||||
|
||||
// Run SSDP server on an interface.
|
||||
func (me *Server) ssdpInterface(if_ net.Interface) {
|
||||
s := ssdp.Server{
|
||||
Interface: if_,
|
||||
Devices: devices(),
|
||||
Services: serviceTypes(),
|
||||
Location: func(ip net.IP) string {
|
||||
return me.location(ip)
|
||||
},
|
||||
Server: serverField,
|
||||
UUID: me.rootDeviceUUID,
|
||||
NotifyInterval: me.NotifyInterval,
|
||||
}
|
||||
if err := s.Init(); err != nil {
|
||||
if if_.Flags&ssdpInterfaceFlags != ssdpInterfaceFlags {
|
||||
// Didn't expect it to work anyway.
|
||||
return
|
||||
}
|
||||
if strings.Contains(err.Error(), "listen") {
|
||||
// OSX has a lot of dud interfaces. Failure to create a socket on
|
||||
// the interface are what we're expecting if the interface is no
|
||||
// good.
|
||||
return
|
||||
}
|
||||
logger.Errorf("error creating ssdp server on %s: %s", if_.Name, err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
logger.Debugf("started SSDP on %s", if_.Name)
|
||||
stopped := make(chan struct{})
|
||||
go func() {
|
||||
defer close(stopped)
|
||||
if err := s.Serve(); err != nil {
|
||||
logger.Errorf("%q: %q\n", if_.Name, err)
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case <-me.closed:
|
||||
// Returning will close the server.
|
||||
case <-stopped:
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
startTime time.Time
|
||||
)
|
||||
|
||||
type Icon struct {
|
||||
Width, Height, Depth int
|
||||
Mimetype string
|
||||
io.ReadSeeker
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
HTTPConn net.Listener
|
||||
FriendlyName string
|
||||
Interfaces []net.Interface
|
||||
httpServeMux *http.ServeMux
|
||||
RootObjectPath string
|
||||
rootDescXML []byte
|
||||
rootDeviceUUID string
|
||||
closed chan struct{}
|
||||
ssdpStopped chan struct{}
|
||||
// The service SOAP handler keyed by service URN.
|
||||
services map[string]UPnPService
|
||||
LogHeaders bool
|
||||
Icons []Icon
|
||||
// Stall event subscription requests until they drop. A workaround for
|
||||
// some bad clients.
|
||||
StallEventSubscribe bool
|
||||
// Time interval between SSPD announces
|
||||
NotifyInterval time.Duration
|
||||
|
||||
txnManager models.TransactionManager
|
||||
sceneServer sceneServer
|
||||
ipWhitelistManager *ipWhitelistManager
|
||||
}
|
||||
|
||||
// UPnP SOAP service.
|
||||
type UPnPService interface {
|
||||
Handle(action string, argsXML []byte, r *http.Request) (respArgs map[string]string, err error)
|
||||
Subscribe(callback []*url.URL, timeoutSeconds int) (sid string, actualTimeout int, err error)
|
||||
Unsubscribe(sid string) error
|
||||
}
|
||||
|
||||
type Cache interface {
|
||||
Set(key interface{}, value interface{})
|
||||
Get(key interface{}) (value interface{}, ok bool)
|
||||
}
|
||||
|
||||
func init() {
|
||||
startTime = time.Now()
|
||||
}
|
||||
|
||||
func xmlMarshalOrPanic(value interface{}) []byte {
|
||||
ret, err := xml.MarshalIndent(value, "", " ")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("xmlMarshalOrPanic failed to marshal %v: %s", value, err))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// TODO: Document the use of this for debugging.
|
||||
type mitmRespWriter struct {
|
||||
http.ResponseWriter
|
||||
loggedHeader bool
|
||||
logHeader bool
|
||||
}
|
||||
|
||||
func (me *mitmRespWriter) WriteHeader(code int) {
|
||||
me.doLogHeader(code)
|
||||
me.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (me *mitmRespWriter) doLogHeader(code int) {
|
||||
if !me.logHeader {
|
||||
return
|
||||
}
|
||||
logger.Debugf("Response: %d", code)
|
||||
for k, v := range me.Header() {
|
||||
logger.Debugf("%s: %s", k, v)
|
||||
}
|
||||
me.loggedHeader = true
|
||||
}
|
||||
|
||||
func (me *mitmRespWriter) Write(b []byte) (int, error) {
|
||||
if !me.loggedHeader {
|
||||
me.doLogHeader(200)
|
||||
}
|
||||
return me.ResponseWriter.Write(b)
|
||||
}
|
||||
|
||||
// Deprecated: the CloseNotifier interface predates Go's context package.
|
||||
// New code should use Request.Context instead.
|
||||
func (me *mitmRespWriter) CloseNotify() <-chan bool {
|
||||
return me.ResponseWriter.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
// Set the SCPD serve paths.
|
||||
func init() {
|
||||
for _, s := range services {
|
||||
p := path.Join("/scpd", s.ServiceId)
|
||||
s.SCPDURL = p
|
||||
}
|
||||
}
|
||||
|
||||
// Install handlers to serve SCPD for each UPnP service.
|
||||
func handleSCPDs(mux *http.ServeMux) {
|
||||
for _, s := range services {
|
||||
mux.HandleFunc(s.SCPDURL, func(serviceDesc string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("content-type", `text/xml; charset="utf-8"`)
|
||||
http.ServeContent(w, r, ".xml", startTime, bytes.NewReader([]byte(serviceDesc)))
|
||||
}
|
||||
}(s.SCPD))
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal SOAP response arguments into a response XML snippet.
|
||||
func marshalSOAPResponse(sa upnp.SoapAction, args map[string]string) []byte {
|
||||
soapArgs := make([]soap.Arg, 0, len(args))
|
||||
for argName, value := range args {
|
||||
soapArgs = append(soapArgs, soap.Arg{
|
||||
XMLName: xml.Name{Local: argName},
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
return []byte(fmt.Sprintf(`<u:%[1]sResponse xmlns:u="%[2]s">%[3]s</u:%[1]sResponse>`, sa.Action, sa.ServiceURN.String(), xmlMarshalOrPanic(soapArgs)))
|
||||
}
|
||||
|
||||
// Handle a SOAP request and return the response arguments or UPnP error.
|
||||
func (me *Server) soapActionResponse(sa upnp.SoapAction, actionRequestXML []byte, r *http.Request) (map[string]string, error) {
|
||||
service, ok := me.services[sa.Type]
|
||||
if !ok {
|
||||
// TODO: What's the invalid service error?!
|
||||
return nil, upnp.Errorf(upnp.InvalidActionErrorCode, "Invalid service: %s", sa.Type)
|
||||
}
|
||||
|
||||
logger.Tracef("%s::Handle %s - %s", sa.Type, sa.Action, actionRequestXML)
|
||||
ret, err := service.Handle(sa.Action, actionRequestXML, r)
|
||||
if err == nil {
|
||||
logger.Tracef("< %v", ret)
|
||||
}
|
||||
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Handle a service control HTTP request.
|
||||
func (me *Server) serviceControlHandler(w http.ResponseWriter, r *http.Request) {
|
||||
clientIp, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
|
||||
ip := net.ParseIP(clientIp).String()
|
||||
if !me.ipWhitelistManager.ipAllowed(ip) {
|
||||
// only log if we haven't seen it
|
||||
if !me.ipWhitelistManager.addRecent(ip) {
|
||||
logger.Infof("not allowed client %s", clientIp)
|
||||
}
|
||||
|
||||
http.Error(w, "forbidden", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
soapActionString := r.Header.Get("SOAPACTION")
|
||||
soapAction, err := upnp.ParseActionHTTPHeader(soapActionString)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var env soap.Envelope
|
||||
if err := xml.NewDecoder(r.Body).Decode(&env); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
// AwoX/1.1 UPnP/1.0 DLNADOC/1.50
|
||||
w.Header().Set("Content-Type", `text/xml; charset="utf-8"`)
|
||||
w.Header().Set("Ext", "")
|
||||
w.Header().Set("Server", serverField)
|
||||
soapRespXML, code := func() ([]byte, int) {
|
||||
respArgs, err := me.soapActionResponse(soapAction, env.Body.Action, r)
|
||||
if err != nil {
|
||||
upnpErr := upnp.ConvertError(err)
|
||||
return xmlMarshalOrPanic(soap.NewFault("UPnPError", upnpErr)), 500
|
||||
}
|
||||
return marshalSOAPResponse(soapAction, respArgs), 200
|
||||
}()
|
||||
bodyStr := fmt.Sprintf(`<?xml version="1.0" encoding="utf-8" standalone="yes"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body>%s</s:Body></s:Envelope>`, soapRespXML)
|
||||
w.WriteHeader(code)
|
||||
if _, err := w.Write([]byte(bodyStr)); err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) serveIcon(w http.ResponseWriter, r *http.Request) {
|
||||
sceneId := r.URL.Query().Get("scene")
|
||||
if sceneId == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var scene *models.Scene
|
||||
me.txnManager.WithReadTxn(context.Background(), func(r models.ReaderRepository) error {
|
||||
idInt, err := strconv.Atoi(sceneId)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
scene, _ = r.Scene().Find(idInt)
|
||||
return nil
|
||||
})
|
||||
|
||||
if scene == nil {
|
||||
return
|
||||
}
|
||||
|
||||
me.sceneServer.ServeScreenshot(scene, w, r)
|
||||
}
|
||||
|
||||
func (me *Server) contentDirectoryInitialEvent(urls []*url.URL, sid string) {
|
||||
body := xmlMarshalOrPanic(upnp.PropertySet{
|
||||
Properties: []upnp.Property{
|
||||
{
|
||||
Variable: upnp.Variable{
|
||||
XMLName: xml.Name{
|
||||
Local: "SystemUpdateID",
|
||||
},
|
||||
Value: "0",
|
||||
},
|
||||
},
|
||||
// upnp.Property{
|
||||
// Variable: upnp.Variable{
|
||||
// XMLName: xml.Name{
|
||||
// Local: "ContainerUpdateIDs",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// upnp.Property{
|
||||
// Variable: upnp.Variable{
|
||||
// XMLName: xml.Name{
|
||||
// Local: "TransferIDs",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
},
|
||||
Space: "urn:schemas-upnp-org:event-1-0",
|
||||
})
|
||||
body = append([]byte(`<?xml version="1.0"?>`+"\n"), body...)
|
||||
for _, _url := range urls {
|
||||
bodyReader := bytes.NewReader(body)
|
||||
req, err := http.NewRequest("NOTIFY", _url.String(), bodyReader)
|
||||
if err != nil {
|
||||
logger.Errorf("Could not create a request to notify %s: %s", _url.String(), err)
|
||||
continue
|
||||
}
|
||||
req.Header["CONTENT-TYPE"] = []string{`text/xml; charset="utf-8"`}
|
||||
req.Header["NT"] = []string{"upnp:event"}
|
||||
req.Header["NTS"] = []string{"upnp:propchange"}
|
||||
req.Header["SID"] = []string{sid}
|
||||
req.Header["SEQ"] = []string{"0"}
|
||||
// req.Header["TRANSFER-ENCODING"] = []string{"chunked"}
|
||||
// req.ContentLength = int64(bodyReader.Len())
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Could not notify %s: %s", _url.String(), err)
|
||||
continue
|
||||
}
|
||||
b, _ := ioutil.ReadAll(resp.Body)
|
||||
if len(b) > 0 {
|
||||
logger.Debug(string(b))
|
||||
}
|
||||
resp.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) contentDirectoryEventSubHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if me.StallEventSubscribe {
|
||||
// I have an LG TV that doesn't like my eventing implementation.
|
||||
// Returning unimplemented (501?) errors, results in repeat subscribe
|
||||
// attempts which hits some kind of error count limit on the TV
|
||||
// causing it to forcefully disconnect. It also won't work if the CDS
|
||||
// service doesn't include an EventSubURL. The best thing I can do is
|
||||
// cause every attempt to subscribe to timeout on the TV end, which
|
||||
// reduces the error rate enough that the TV continues to operate
|
||||
// without eventing.
|
||||
//
|
||||
// I've not found a reliable way to identify this TV, since it and
|
||||
// others don't seem to include any client-identifying headers on
|
||||
// SUBSCRIBE requests.
|
||||
//
|
||||
// TODO: Get eventing to work with the problematic TV.
|
||||
t := time.Now()
|
||||
<-r.Context().Done()
|
||||
logger.Debugf("stalled subscribe connection went away after %s", time.Since(t))
|
||||
return
|
||||
}
|
||||
// The following code is a work in progress. It partially implements
|
||||
// the spec on eventing but hasn't been completed as I have nothing to
|
||||
// test it with.
|
||||
service := me.services["ContentDirectory"]
|
||||
if r.Method == "SUBSCRIBE" && r.Header.Get("SID") == "" {
|
||||
urls := upnp.ParseCallbackURLs(r.Header.Get("CALLBACK"))
|
||||
var timeout int
|
||||
fmt.Sscanf(r.Header.Get("TIMEOUT"), "Second-%d", &timeout)
|
||||
sid, timeout, _ := service.Subscribe(urls, timeout)
|
||||
w.Header()["SID"] = []string{sid}
|
||||
w.Header()["TIMEOUT"] = []string{fmt.Sprintf("Second-%d", timeout)}
|
||||
// TODO: Shouldn't have to do this to get headers logged.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
go func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
me.contentDirectoryInitialEvent(urls, sid)
|
||||
}()
|
||||
} else if r.Method == "SUBSCRIBE" {
|
||||
http.Error(w, "meh", http.StatusPreconditionFailed)
|
||||
} else {
|
||||
logger.Debugf("unhandled event method: %s", r.Method)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) initMux(mux *http.ServeMux) {
|
||||
mux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
|
||||
resp.Header().Set("content-type", "text/html")
|
||||
err := rootTmpl.Execute(resp, struct {
|
||||
Readonly bool
|
||||
Path string
|
||||
}{
|
||||
true,
|
||||
me.RootObjectPath,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
})
|
||||
mux.HandleFunc(contentDirectoryEventSubURL, me.contentDirectoryEventSubHandler)
|
||||
mux.HandleFunc(iconPath, me.serveIcon)
|
||||
mux.HandleFunc(resPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
sceneId := r.URL.Query().Get("scene")
|
||||
var scene *models.Scene
|
||||
me.txnManager.WithReadTxn(context.Background(), func(r models.ReaderRepository) error {
|
||||
sceneIdInt, err := strconv.Atoi(sceneId)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
scene, _ = r.Scene().Find(sceneIdInt)
|
||||
return nil
|
||||
})
|
||||
|
||||
if scene == nil {
|
||||
return
|
||||
}
|
||||
|
||||
me.sceneServer.StreamSceneDirect(scene, w, r)
|
||||
})
|
||||
mux.HandleFunc(rootDescPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("content-type", `text/xml; charset="utf-8"`)
|
||||
w.Header().Set("content-length", fmt.Sprint(len(me.rootDescXML)))
|
||||
w.Header().Set("server", serverField)
|
||||
w.Write(me.rootDescXML)
|
||||
})
|
||||
handleSCPDs(mux)
|
||||
mux.HandleFunc(serviceControlURL, me.serviceControlHandler)
|
||||
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
||||
for i, di := range me.Icons {
|
||||
mux.HandleFunc(fmt.Sprintf("%s/%d", deviceIconPath, i), func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", di.Mimetype)
|
||||
http.ServeContent(w, r, "", time.Time{}, di.ReadSeeker)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) initServices() {
|
||||
me.services = map[string]UPnPService{
|
||||
"ContentDirectory": &contentDirectoryService{
|
||||
Server: me,
|
||||
txnManager: me.txnManager,
|
||||
},
|
||||
"ConnectionManager": &connectionManagerService{
|
||||
Server: me,
|
||||
},
|
||||
"X_MS_MediaReceiverRegistrar": &mediaReceiverRegistrarService{
|
||||
Server: me,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) Serve() (err error) {
|
||||
me.initServices()
|
||||
me.closed = make(chan struct{})
|
||||
if me.HTTPConn == nil {
|
||||
me.HTTPConn, err = net.Listen("tcp", "")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if me.Interfaces == nil {
|
||||
ifs, err := net.Interfaces()
|
||||
if err != nil {
|
||||
logger.Errorf(err.Error())
|
||||
}
|
||||
var tmp []net.Interface
|
||||
for _, if_ := range ifs {
|
||||
if if_.Flags&net.FlagUp == 0 || if_.MTU <= 0 {
|
||||
continue
|
||||
}
|
||||
tmp = append(tmp, if_)
|
||||
}
|
||||
me.Interfaces = tmp
|
||||
}
|
||||
me.httpServeMux = http.NewServeMux()
|
||||
me.rootDeviceUUID = makeDeviceUuid(me.FriendlyName)
|
||||
me.rootDescXML, err = xml.MarshalIndent(
|
||||
upnp.DeviceDesc{
|
||||
SpecVersion: upnp.SpecVersion{Major: 1, Minor: 0},
|
||||
Device: upnp.Device{
|
||||
DeviceType: rootDeviceType,
|
||||
FriendlyName: me.FriendlyName,
|
||||
Manufacturer: me.FriendlyName,
|
||||
ModelName: rootDeviceModelName,
|
||||
UDN: me.rootDeviceUUID,
|
||||
ServiceList: func() (ss []upnp.Service) {
|
||||
for _, s := range services {
|
||||
ss = append(ss, s.Service)
|
||||
}
|
||||
return
|
||||
}(),
|
||||
IconList: func() (ret []upnp.Icon) {
|
||||
for i, di := range me.Icons {
|
||||
ret = append(ret, upnp.Icon{
|
||||
Height: di.Height,
|
||||
Width: di.Width,
|
||||
Depth: di.Depth,
|
||||
Mimetype: di.Mimetype,
|
||||
URL: fmt.Sprintf("%s/%d", deviceIconPath, i),
|
||||
})
|
||||
}
|
||||
return
|
||||
}(),
|
||||
},
|
||||
},
|
||||
" ", " ")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
me.rootDescXML = append([]byte(`<?xml version="1.0"?>`), me.rootDescXML...)
|
||||
logger.Debug("HTTP srv on", me.HTTPConn.Addr())
|
||||
me.initMux(me.httpServeMux)
|
||||
me.ssdpStopped = make(chan struct{})
|
||||
go func() {
|
||||
me.doSSDP()
|
||||
close(me.ssdpStopped)
|
||||
}()
|
||||
return me.serveHTTP()
|
||||
}
|
||||
|
||||
func (me *Server) Close() (err error) {
|
||||
close(me.closed)
|
||||
err = me.HTTPConn.Close()
|
||||
<-me.ssdpStopped
|
||||
return
|
||||
}
|
||||
|
||||
func didl_lite(chardata string) string {
|
||||
return `<DIDL-Lite` +
|
||||
` xmlns:dc="http://purl.org/dc/elements/1.1/"` +
|
||||
` xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"` +
|
||||
` xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"` +
|
||||
` xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/">` +
|
||||
chardata +
|
||||
`</DIDL-Lite>`
|
||||
}
|
||||
|
||||
func (me *Server) location(ip net.IP) string {
|
||||
url := url.URL{
|
||||
Scheme: "http",
|
||||
Host: (&net.TCPAddr{
|
||||
IP: ip,
|
||||
Port: me.httpPort(),
|
||||
}).String(),
|
||||
Path: rootDescPath,
|
||||
}
|
||||
return url.String()
|
||||
}
|
||||
47
pkg/dlna/html.go
Normal file
47
pkg/dlna/html.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package dlna
|
||||
|
||||
// From: https://github.com/anacrolix/dms
|
||||
// Copyright (c) 2012, Matt Joiner <anacrolix@gmail.com>.
|
||||
// All rights reserved.
|
||||
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the <organization> nor the
|
||||
// names of its contributors may be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
)
|
||||
|
||||
var (
|
||||
rootTmpl *template.Template
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootTmpl = template.Must(template.New("root").Parse(
|
||||
`<form method="post">
|
||||
Path: <input type="text"
|
||||
name="path"
|
||||
{{if .Readonly}} readonly="readonly"{{end}}
|
||||
value="{{.Path}}"
|
||||
/>
|
||||
<input type="submit" value="Update"{{if .Readonly}} disabled="disabled"{{end}}/>
|
||||
</form>`))
|
||||
}
|
||||
27
pkg/dlna/mrrs.go
Normal file
27
pkg/dlna/mrrs.go
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package dlna
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/anacrolix/dms/upnp"
|
||||
)
|
||||
|
||||
type mediaReceiverRegistrarService struct {
|
||||
*Server
|
||||
upnp.Eventing
|
||||
}
|
||||
|
||||
func (mrrs *mediaReceiverRegistrarService) Handle(action string, argsXML []byte, r *http.Request) (map[string]string, error) {
|
||||
switch action {
|
||||
case "IsAuthorized", "IsValidated":
|
||||
return map[string]string{
|
||||
"Result": "1",
|
||||
}, nil
|
||||
case "RegisterDevice":
|
||||
return map[string]string{
|
||||
"RegistrationRespMsg": mrrs.rootDeviceUUID,
|
||||
}, nil
|
||||
default:
|
||||
return nil, upnp.InvalidActionError
|
||||
}
|
||||
}
|
||||
80
pkg/dlna/paging.go
Normal file
80
pkg/dlna/paging.go
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package dlna
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type scenePager struct {
|
||||
sceneFilter *models.SceneFilterType
|
||||
parentID string
|
||||
}
|
||||
|
||||
func (p *scenePager) getPageID(page int) string {
|
||||
return p.parentID + "/page/" + strconv.Itoa(page)
|
||||
}
|
||||
|
||||
func (p *scenePager) getPages(r models.ReaderRepository, total int) ([]interface{}, error) {
|
||||
var objs []interface{}
|
||||
|
||||
// get the first scene of each page to set an appropriate title
|
||||
pages := int(math.Ceil(float64(total) / float64(pageSize)))
|
||||
|
||||
singlePageSize := 1
|
||||
sort := "title"
|
||||
findFilter := &models.FindFilterType{
|
||||
PerPage: &singlePageSize,
|
||||
Sort: &sort,
|
||||
}
|
||||
|
||||
for page := 1; page <= pages; page++ {
|
||||
// TODO - this is really slow. Not sure if there's a better way
|
||||
title := fmt.Sprintf("Page %d", page)
|
||||
if pages <= 10 || (page-1)%(pages/10) == 0 {
|
||||
thisPage := ((page - 1) * pageSize) + 1
|
||||
findFilter.Page = &thisPage
|
||||
scenes, _, err := r.Scene().Query(p.sceneFilter, findFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sceneTitle := scenes[0].GetTitle()
|
||||
|
||||
// use the first three letters as a prefix
|
||||
if len(sceneTitle) > 3 {
|
||||
sceneTitle = sceneTitle[0:3]
|
||||
}
|
||||
|
||||
title = title + fmt.Sprintf(" (%s...)", sceneTitle)
|
||||
}
|
||||
|
||||
objs = append(objs, makeStorageFolder(p.getPageID(page), title, p.parentID))
|
||||
}
|
||||
|
||||
return objs, nil
|
||||
}
|
||||
|
||||
func (p *scenePager) getPageVideos(r models.ReaderRepository, page int, host string) ([]interface{}, error) {
|
||||
var objs []interface{}
|
||||
|
||||
sort := "title"
|
||||
findFilter := &models.FindFilterType{
|
||||
PerPage: &pageSize,
|
||||
Page: &page,
|
||||
Sort: &sort,
|
||||
}
|
||||
|
||||
scenes, _, err := r.Scene().Query(p.sceneFilter, findFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range scenes {
|
||||
objs = append(objs, sceneToContainer(s, p.parentID, host))
|
||||
}
|
||||
|
||||
return objs, nil
|
||||
}
|
||||
298
pkg/dlna/service.go
Normal file
298
pkg/dlna/service.go
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
package dlna
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
)
|
||||
|
||||
type dmsConfig struct {
|
||||
Path string
|
||||
IfNames []string
|
||||
Http string
|
||||
FriendlyName string
|
||||
LogHeaders bool
|
||||
StallEventSubscribe bool
|
||||
NotifyInterval time.Duration
|
||||
}
|
||||
|
||||
type sceneServer interface {
|
||||
StreamSceneDirect(scene *models.Scene, w http.ResponseWriter, r *http.Request)
|
||||
ServeScreenshot(scene *models.Scene, w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
txnManager models.TransactionManager
|
||||
config *config.Instance
|
||||
sceneServer sceneServer
|
||||
ipWhitelistMgr *ipWhitelistManager
|
||||
|
||||
server *Server
|
||||
running bool
|
||||
mutex sync.Mutex
|
||||
|
||||
startTimer *time.Timer
|
||||
startTime *time.Time
|
||||
stopTimer *time.Timer
|
||||
stopTime *time.Time
|
||||
}
|
||||
|
||||
func (s *Service) getInterfaces() ([]net.Interface, error) {
|
||||
var ifs []net.Interface
|
||||
var err error
|
||||
ifNames := s.config.GetDLNAInterfaces()
|
||||
|
||||
if len(ifNames) == 0 {
|
||||
ifs, err = net.Interfaces()
|
||||
} else {
|
||||
for _, n := range ifNames {
|
||||
if_, err := net.InterfaceByName(n)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting interface for name %s: %s", n, err.Error())
|
||||
}
|
||||
|
||||
if if_ != nil {
|
||||
ifs = append(ifs, *if_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tmp []net.Interface
|
||||
for _, if_ := range ifs {
|
||||
if if_.Flags&net.FlagUp == 0 || if_.MTU <= 0 {
|
||||
continue
|
||||
}
|
||||
tmp = append(tmp, if_)
|
||||
}
|
||||
ifs = tmp
|
||||
return ifs, nil
|
||||
}
|
||||
|
||||
func (s *Service) init() error {
|
||||
friendlyName := s.config.GetDLNAServerName()
|
||||
if friendlyName == "" {
|
||||
friendlyName = "stash"
|
||||
}
|
||||
|
||||
var dmsConfig = &dmsConfig{
|
||||
Path: "",
|
||||
IfNames: s.config.GetDLNADefaultIPWhitelist(),
|
||||
Http: ":1338",
|
||||
FriendlyName: friendlyName,
|
||||
LogHeaders: false,
|
||||
NotifyInterval: 30 * time.Second,
|
||||
}
|
||||
|
||||
interfaces, err := s.getInterfaces()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.server = &Server{
|
||||
txnManager: s.txnManager,
|
||||
sceneServer: s.sceneServer,
|
||||
ipWhitelistManager: s.ipWhitelistMgr,
|
||||
Interfaces: interfaces,
|
||||
HTTPConn: func() net.Listener {
|
||||
conn, err := net.Listen("tcp", dmsConfig.Http)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
return conn
|
||||
}(),
|
||||
FriendlyName: dmsConfig.FriendlyName,
|
||||
RootObjectPath: filepath.Clean(dmsConfig.Path),
|
||||
LogHeaders: dmsConfig.LogHeaders,
|
||||
// Icons: []Icon{
|
||||
// {
|
||||
// Width: 48,
|
||||
// Height: 48,
|
||||
// Depth: 8,
|
||||
// Mimetype: "image/png",
|
||||
// //ReadSeeker: readIcon(config.Config.Interfaces.DLNA.ServiceImage, 48),
|
||||
// },
|
||||
// {
|
||||
// Width: 128,
|
||||
// Height: 128,
|
||||
// Depth: 8,
|
||||
// Mimetype: "image/png",
|
||||
// //ReadSeeker: readIcon(config.Config.Interfaces.DLNA.ServiceImage, 128),
|
||||
// },
|
||||
// },
|
||||
StallEventSubscribe: dmsConfig.StallEventSubscribe,
|
||||
NotifyInterval: dmsConfig.NotifyInterval,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// func getIconReader(fn string) (io.Reader, error) {
|
||||
// b, err := assets.ReadFile("dlna/" + fn + ".png")
|
||||
// return bytes.NewReader(b), err
|
||||
// }
|
||||
|
||||
// func readIcon(path string, size uint) *bytes.Reader {
|
||||
// r, err := getIconReader(path)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// imageData, _, err := image.Decode(r)
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// return resizeImage(imageData, size)
|
||||
// }
|
||||
|
||||
// func resizeImage(imageData image.Image, size uint) *bytes.Reader {
|
||||
// img := resize.Resize(size, size, imageData, resize.Lanczos3)
|
||||
// var buff bytes.Buffer
|
||||
// png.Encode(&buff, img)
|
||||
// return bytes.NewReader(buff.Bytes())
|
||||
// }
|
||||
|
||||
// NewService initialises and returns a new DLNA service.
|
||||
func NewService(txnManager models.TransactionManager, cfg *config.Instance, sceneServer sceneServer) *Service {
|
||||
ret := &Service{
|
||||
txnManager: txnManager,
|
||||
sceneServer: sceneServer,
|
||||
config: cfg,
|
||||
ipWhitelistMgr: &ipWhitelistManager{
|
||||
config: cfg,
|
||||
},
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Start starts the DLNA service. If duration is provided, then the service
|
||||
// is stopped after the duration has elapsed.
|
||||
func (s *Service) Start(duration *time.Duration) error {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if !s.running {
|
||||
if err := s.init(); err != nil {
|
||||
logger.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
logger.Info("Starting DLNA")
|
||||
if err := s.server.Serve(); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}()
|
||||
s.running = true
|
||||
|
||||
if s.startTimer != nil {
|
||||
s.startTimer.Stop()
|
||||
s.startTimer = nil
|
||||
s.startTime = nil
|
||||
}
|
||||
}
|
||||
|
||||
if duration != nil {
|
||||
// clear the existing stop timer
|
||||
if s.stopTimer != nil {
|
||||
s.stopTimer.Stop()
|
||||
s.stopTime = nil
|
||||
}
|
||||
|
||||
if s.stopTimer == nil {
|
||||
s.stopTimer = time.AfterFunc(*duration, func() {
|
||||
s.Stop(nil)
|
||||
})
|
||||
t := time.Now().Add(*duration)
|
||||
s.stopTime = &t
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the DLNA service. If duration is provided, then the service
|
||||
// is started after the duration has elapsed.
|
||||
func (s *Service) Stop(duration *time.Duration) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if s.running {
|
||||
logger.Info("Stopping DLNA")
|
||||
err := s.server.Close()
|
||||
if err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
s.running = false
|
||||
|
||||
if s.stopTimer != nil {
|
||||
s.stopTimer.Stop()
|
||||
s.stopTimer = nil
|
||||
s.stopTime = nil
|
||||
}
|
||||
}
|
||||
|
||||
if duration != nil {
|
||||
// clear the existing stop timer
|
||||
if s.startTimer != nil {
|
||||
s.startTimer.Stop()
|
||||
}
|
||||
|
||||
if s.startTimer == nil {
|
||||
s.startTimer = time.AfterFunc(*duration, func() {
|
||||
s.Start(nil)
|
||||
})
|
||||
t := time.Now().Add(*duration)
|
||||
s.startTime = &t
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsRunning returns true if the DLNA service is running.
|
||||
func (s *Service) IsRunning() bool {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
return s.running
|
||||
}
|
||||
|
||||
func (s *Service) Status() *models.DLNAStatus {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
ret := &models.DLNAStatus{
|
||||
Running: s.running,
|
||||
RecentIPAddresses: s.ipWhitelistMgr.getRecent(),
|
||||
AllowedIPAddresses: s.ipWhitelistMgr.getTempAllowed(),
|
||||
}
|
||||
|
||||
if s.startTime != nil {
|
||||
t := *s.startTime
|
||||
ret.Until = &t
|
||||
}
|
||||
|
||||
if s.stopTime != nil {
|
||||
t := *s.stopTime
|
||||
ret.Until = &t
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *Service) AddTempDLNAIP(pattern string, duration *time.Duration) {
|
||||
s.ipWhitelistMgr.allowPattern(pattern, duration)
|
||||
}
|
||||
|
||||
func (s *Service) RemoveTempDLNAIP(pattern string) bool {
|
||||
return s.ipWhitelistMgr.removePattern(pattern)
|
||||
}
|
||||
204
pkg/dlna/whitelist.go
Normal file
204
pkg/dlna/whitelist.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
package dlna
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
// only keep the 10 most recent IP addresses
|
||||
const recentListLength = 10
|
||||
|
||||
const wildcard = "*"
|
||||
|
||||
type tempIPWhitelist struct {
|
||||
pattern string
|
||||
until *time.Time
|
||||
}
|
||||
|
||||
type ipWhitelistManager struct {
|
||||
recentIPAddresses []string
|
||||
config *config.Instance
|
||||
tempWhitelist []tempIPWhitelist
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// addRecent adds the provided address to the recent IP addresses list if it
|
||||
// was not already present. Returns true if it was already present.
|
||||
func (m *ipWhitelistManager) addRecent(addr string) bool {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
i := utils.StrIndex(m.recentIPAddresses, addr)
|
||||
if i != -1 {
|
||||
if i == 0 {
|
||||
// don't do anything if it's already at the start
|
||||
return true
|
||||
}
|
||||
|
||||
// remove from the list
|
||||
m.recentIPAddresses = append(m.recentIPAddresses[:i], m.recentIPAddresses[i+1:]...)
|
||||
}
|
||||
|
||||
// add to the top of the list
|
||||
m.recentIPAddresses = append([]string{addr}, m.recentIPAddresses...)
|
||||
|
||||
if len(m.recentIPAddresses) > recentListLength {
|
||||
m.recentIPAddresses = m.recentIPAddresses[:recentListLength]
|
||||
}
|
||||
|
||||
return i != -1
|
||||
}
|
||||
|
||||
func (m *ipWhitelistManager) getRecent() []string {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
return m.recentIPAddresses
|
||||
}
|
||||
|
||||
func (m *ipWhitelistManager) getTempAllowed() []*models.Dlnaip {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
var ret []*models.Dlnaip
|
||||
|
||||
now := time.Now()
|
||||
removeExpired := false
|
||||
for _, a := range m.tempWhitelist {
|
||||
if a.until != nil && now.After(*a.until) {
|
||||
removeExpired = true
|
||||
continue
|
||||
}
|
||||
|
||||
ret = append(ret, &models.Dlnaip{
|
||||
IPAddress: a.pattern,
|
||||
Until: a.until,
|
||||
})
|
||||
}
|
||||
|
||||
if removeExpired {
|
||||
m.removeExpiredWhitelists()
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (m *ipWhitelistManager) ipAllowed(addr string) bool {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
for _, a := range m.config.GetDLNADefaultIPWhitelist() {
|
||||
if a == wildcard {
|
||||
return true
|
||||
}
|
||||
|
||||
if addr == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
removeExpired := false
|
||||
for _, a := range m.tempWhitelist {
|
||||
if a.until != nil && now.After(*a.until) {
|
||||
removeExpired = true
|
||||
continue
|
||||
}
|
||||
|
||||
if a.pattern == wildcard {
|
||||
return true
|
||||
}
|
||||
|
||||
if addr == a.pattern {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if removeExpired {
|
||||
m.removeExpiredWhitelists()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *ipWhitelistManager) removeExpiredWhitelists() {
|
||||
// assumes mutex is already held
|
||||
var newList []tempIPWhitelist
|
||||
now := time.Now()
|
||||
|
||||
for _, a := range m.tempWhitelist {
|
||||
if a.until != nil && now.After(*a.until) {
|
||||
continue
|
||||
}
|
||||
|
||||
newList = append(newList, a)
|
||||
}
|
||||
|
||||
m.tempWhitelist = newList
|
||||
}
|
||||
|
||||
func (m *ipWhitelistManager) allowPattern(pattern string, duration *time.Duration) {
|
||||
if pattern == "" {
|
||||
return
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
// overwrite existing
|
||||
var newList []tempIPWhitelist
|
||||
found := false
|
||||
|
||||
var until *time.Time
|
||||
|
||||
if duration != nil {
|
||||
u := time.Now().Add(*duration)
|
||||
until = &u
|
||||
}
|
||||
|
||||
for _, a := range m.tempWhitelist {
|
||||
if a.pattern == pattern {
|
||||
a.until = until
|
||||
found = true
|
||||
}
|
||||
|
||||
newList = append(newList, a)
|
||||
}
|
||||
|
||||
if !found {
|
||||
newList = append(newList, tempIPWhitelist{
|
||||
pattern: pattern,
|
||||
until: until,
|
||||
})
|
||||
}
|
||||
|
||||
m.tempWhitelist = newList
|
||||
}
|
||||
|
||||
func (m *ipWhitelistManager) removePattern(pattern string) bool {
|
||||
if pattern == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
var newList []tempIPWhitelist
|
||||
found := false
|
||||
|
||||
for _, a := range m.tempWhitelist {
|
||||
if a.pattern == pattern {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
|
||||
newList = append(newList, a)
|
||||
}
|
||||
|
||||
m.tempWhitelist = newList
|
||||
return found
|
||||
}
|
||||
111
pkg/dlna/xmsr-service-desc.go
Normal file
111
pkg/dlna/xmsr-service-desc.go
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
package dlna
|
||||
|
||||
// from https://github.com/rclone/rclone
|
||||
// Copyright (C) 2012 by Nick Craig-Wood http://www.craig-wood.com/nick/
|
||||
|
||||
// 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.
|
||||
|
||||
const xmsMediaReceiverServiceDescription = `<?xml version="1.0" ?>
|
||||
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
|
||||
<specVersion>
|
||||
<major>1</major>
|
||||
<minor>0</minor>
|
||||
</specVersion>
|
||||
<actionList>
|
||||
<action>
|
||||
<name>IsAuthorized</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>DeviceID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_DeviceID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Result</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>RegisterDevice</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>RegistrationReqMsg</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_RegistrationReqMsg</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>RegistrationRespMsg</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_RegistrationRespMsg</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
<action>
|
||||
<name>IsValidated</name>
|
||||
<argumentList>
|
||||
<argument>
|
||||
<name>DeviceID</name>
|
||||
<direction>in</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_DeviceID</relatedStateVariable>
|
||||
</argument>
|
||||
<argument>
|
||||
<name>Result</name>
|
||||
<direction>out</direction>
|
||||
<relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
|
||||
</argument>
|
||||
</argumentList>
|
||||
</action>
|
||||
</actionList>
|
||||
<serviceStateTable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_DeviceID</name>
|
||||
<dataType>string</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_Result</name>
|
||||
<dataType>int</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_RegistrationReqMsg</name>
|
||||
<dataType>bin.base64</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="no">
|
||||
<name>A_ARG_TYPE_RegistrationRespMsg</name>
|
||||
<dataType>bin.base64</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>AuthorizationGrantedUpdateID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>AuthorizationDeniedUpdateID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>ValidationSucceededUpdateID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
<stateVariable sendEvents="yes">
|
||||
<name>ValidationRevokedUpdateID</name>
|
||||
<dataType>ui4</dataType>
|
||||
</stateVariable>
|
||||
</serviceStateTable>
|
||||
</scpd>`
|
||||
|
|
@ -124,6 +124,12 @@ const CSSEnabled = "cssEnabled"
|
|||
const WallPlayback = "wall_playback"
|
||||
const SlideshowDelay = "slideshow_delay"
|
||||
|
||||
// DLNA options
|
||||
const DLNAServerName = "dlna.server_name"
|
||||
const DLNADefaultEnabled = "dlna.default_enabled"
|
||||
const DLNADefaultIPWhitelist = "dlna.default_whitelist"
|
||||
const DLNAInterfaces = "dlna.interfaces"
|
||||
|
||||
// Logging options
|
||||
const LogFile = "logFile"
|
||||
const LogOut = "logOut"
|
||||
|
|
@ -627,6 +633,29 @@ func (i *Instance) GetCSSEnabled() bool {
|
|||
return viper.GetBool(CSSEnabled)
|
||||
}
|
||||
|
||||
// GetDLNAServerName returns the visible name of the DLNA server. If empty,
|
||||
// "stash" will be used.
|
||||
func (i *Instance) GetDLNAServerName() string {
|
||||
return viper.GetString(DLNAServerName)
|
||||
}
|
||||
|
||||
// GetDLNADefaultEnabled returns true if the DLNA is enabled by default.
|
||||
func (i *Instance) GetDLNADefaultEnabled() bool {
|
||||
return viper.GetBool(DLNADefaultEnabled)
|
||||
}
|
||||
|
||||
// GetDLNADefaultIPWhitelist returns a list of IP addresses/wildcards that
|
||||
// are allowed to use the DLNA service.
|
||||
func (i *Instance) GetDLNADefaultIPWhitelist() []string {
|
||||
return viper.GetStringSlice(DLNADefaultIPWhitelist)
|
||||
}
|
||||
|
||||
// GetDLNAInterfaces returns a list of interface names to expose DLNA on. If
|
||||
// empty, runs on all interfaces.
|
||||
func (i *Instance) GetDLNAInterfaces() []string {
|
||||
return viper.GetStringSlice(DLNAInterfaces)
|
||||
}
|
||||
|
||||
// GetLogFile returns the filename of the file to output logs to.
|
||||
// An empty string means that file logging will be disabled.
|
||||
func (i *Instance) GetLogFile() string {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/stashapp/stash/pkg/database"
|
||||
"github.com/stashapp/stash/pkg/dlna"
|
||||
"github.com/stashapp/stash/pkg/ffmpeg"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
|
|
@ -35,6 +36,8 @@ type singleton struct {
|
|||
|
||||
DownloadStore *DownloadStore
|
||||
|
||||
DLNAService *dlna.Service
|
||||
|
||||
TxnManager models.TransactionManager
|
||||
}
|
||||
|
||||
|
|
@ -66,6 +69,11 @@ func Initialize() *singleton {
|
|||
TxnManager: sqlite.NewTransactionManager(),
|
||||
}
|
||||
|
||||
sceneServer := SceneServer{
|
||||
TXNManager: instance.TxnManager,
|
||||
}
|
||||
instance.DLNAService = dlna.NewService(instance.TxnManager, instance.Config, &sceneServer)
|
||||
|
||||
if !cfg.IsNewSystem() {
|
||||
logger.Infof("using config file: %s", cfg.GetConfigFile())
|
||||
|
||||
|
|
@ -89,6 +97,11 @@ func Initialize() *singleton {
|
|||
}
|
||||
|
||||
initFFMPEG()
|
||||
|
||||
// if DLNA is enabled, start it now
|
||||
if instance.Config.GetDLNADefaultEnabled() {
|
||||
instance.DLNAService.Start(nil)
|
||||
}
|
||||
})
|
||||
|
||||
return instance
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ import (
|
|||
|
||||
"github.com/stashapp/stash/pkg/ffmpeg"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
"github.com/stashapp/stash/pkg/manager/config"
|
||||
"github.com/stashapp/stash/pkg/models"
|
||||
"github.com/stashapp/stash/pkg/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -65,3 +68,33 @@ func KillRunningStreams(path string) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type SceneServer struct {
|
||||
TXNManager models.TransactionManager
|
||||
}
|
||||
|
||||
func (s *SceneServer) StreamSceneDirect(scene *models.Scene, w http.ResponseWriter, r *http.Request) {
|
||||
fileNamingAlgo := config.GetInstance().GetVideoFileNamingAlgorithm()
|
||||
|
||||
filepath := GetInstance().Paths.Scene.GetStreamPath(scene.Path, scene.GetHash(fileNamingAlgo))
|
||||
RegisterStream(filepath, &w)
|
||||
http.ServeFile(w, r, filepath)
|
||||
WaitAndDeregisterStream(filepath, &w, r)
|
||||
}
|
||||
|
||||
func (s *SceneServer) ServeScreenshot(scene *models.Scene, w http.ResponseWriter, r *http.Request) {
|
||||
filepath := GetInstance().Paths.Scene.GetScreenshotPath(scene.GetHash(config.GetInstance().GetVideoFileNamingAlgorithm()))
|
||||
|
||||
// fall back to the scene image blob if the file isn't present
|
||||
screenshotExists, _ := utils.FileExists(filepath)
|
||||
if screenshotExists {
|
||||
http.ServeFile(w, r, filepath)
|
||||
} else {
|
||||
var cover []byte
|
||||
s.TXNManager.WithReadTxn(r.Context(), func(repo models.ReaderRepository) error {
|
||||
cover, _ = repo.Scene().GetCover(scene.ID)
|
||||
return nil
|
||||
})
|
||||
utils.ServeImage(cover, w, r)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
### ✨ New Features
|
||||
* Added [DLNA server](/settings?tab=dlna). ([#1364](https://github.com/stashapp/stash/pull/1364))
|
||||
|
||||
### 🎨 Improvements
|
||||
* Skip scanning directories if path matches image and video exclude patterns. ([#1382](https://github.com/stashapp/stash/pull/1382))
|
||||
* Add button to remove studio stash ID. ([#1378](https://github.com/stashapp/stash/pull/1378))
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { SettingsTasksPanel } from "./SettingsTasksPanel/SettingsTasksPanel";
|
|||
import { SettingsPluginsPanel } from "./SettingsPluginsPanel";
|
||||
import { SettingsScrapersPanel } from "./SettingsScrapersPanel";
|
||||
import { SettingsToolsPanel } from "./SettingsToolsPanel";
|
||||
import { SettingsDLNAPanel } from "./SettingsDLNAPanel";
|
||||
|
||||
export const Settings: React.FC = () => {
|
||||
const location = useLocation();
|
||||
|
|
@ -21,7 +22,7 @@ export const Settings: React.FC = () => {
|
|||
return (
|
||||
<Card className="col col-lg-9 mx-auto">
|
||||
<Tab.Container
|
||||
defaultActiveKey={defaultTab}
|
||||
activeKey={defaultTab}
|
||||
id="configuration-tabs"
|
||||
onSelect={(tab) => tab && onSelect(tab)}
|
||||
>
|
||||
|
|
@ -37,6 +38,9 @@ export const Settings: React.FC = () => {
|
|||
<Nav.Item>
|
||||
<Nav.Link eventKey="tasks">Tasks</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="dlna">DLNA</Nav.Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Nav.Link eventKey="tools">Tools</Nav.Link>
|
||||
</Nav.Item>
|
||||
|
|
@ -66,6 +70,9 @@ export const Settings: React.FC = () => {
|
|||
<Tab.Pane eventKey="tasks">
|
||||
<SettingsTasksPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="dlna" unmountOnExit>
|
||||
<SettingsDLNAPanel />
|
||||
</Tab.Pane>
|
||||
<Tab.Pane eventKey="tools" unmountOnExit>
|
||||
<SettingsToolsPanel />
|
||||
</Tab.Pane>
|
||||
|
|
|
|||
532
ui/v2.5/src/components/Settings/SettingsDLNAPanel.tsx
Normal file
532
ui/v2.5/src/components/Settings/SettingsDLNAPanel.tsx
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
import React, { useState } from "react";
|
||||
import { Formik, useFormikContext } from "formik";
|
||||
import { Button, Form } from "react-bootstrap";
|
||||
import { Prompt } from "react-router";
|
||||
import * as yup from "yup";
|
||||
import {
|
||||
useConfiguration,
|
||||
useConfigureDLNA,
|
||||
useDisableDLNA,
|
||||
useDLNAStatus,
|
||||
useEnableDLNA,
|
||||
useAddTempDLNAIP,
|
||||
useRemoveTempDLNAIP,
|
||||
} from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { DurationInput, Icon, LoadingIndicator, Modal } from "../Shared";
|
||||
import { StringListInput } from "../Shared/StringListInput";
|
||||
|
||||
export const SettingsDLNAPanel: React.FC = () => {
|
||||
const Toast = useToast();
|
||||
|
||||
// undefined to hide dialog, true for enable, false for disable
|
||||
const [enableDisable, setEnableDisable] = useState<boolean | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const [enableUntilRestart, setEnableUntilRestart] = useState<boolean>(false);
|
||||
const [enableDuration, setEnableDuration] = useState<number | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const [ipEntry, setIPEntry] = useState<string>("");
|
||||
const [tempIP, setTempIP] = useState<string | undefined>();
|
||||
|
||||
const { data, refetch: configRefetch } = useConfiguration();
|
||||
const { data: statusData, loading, refetch: statusRefetch } = useDLNAStatus();
|
||||
|
||||
const [updateDLNAConfig] = useConfigureDLNA();
|
||||
|
||||
const [enableDLNA] = useEnableDLNA();
|
||||
const [disableDLNA] = useDisableDLNA();
|
||||
const [addTempDLANIP] = useAddTempDLNAIP();
|
||||
const [removeTempDLNAIP] = useRemoveTempDLNAIP();
|
||||
|
||||
if (loading) return <LoadingIndicator />;
|
||||
|
||||
// settings
|
||||
const schema = yup.object({
|
||||
serverName: yup.string(),
|
||||
enabled: yup.boolean().required(),
|
||||
whitelistedIPs: yup.array(yup.string().required()).required(),
|
||||
interfaces: yup.array(yup.string().required()).required(),
|
||||
});
|
||||
|
||||
interface IConfigValues {
|
||||
serverName: string;
|
||||
enabled: boolean;
|
||||
whitelistedIPs: string[];
|
||||
interfaces: string[];
|
||||
}
|
||||
|
||||
const initialValues: IConfigValues = {
|
||||
serverName: data?.configuration.dlna.serverName ?? "",
|
||||
enabled: data?.configuration.dlna.enabled ?? false,
|
||||
whitelistedIPs: data?.configuration.dlna.whitelistedIPs ?? [],
|
||||
interfaces: data?.configuration.dlna.interfaces ?? [],
|
||||
};
|
||||
|
||||
async function onSave(input: IConfigValues) {
|
||||
try {
|
||||
await updateDLNAConfig({
|
||||
variables: {
|
||||
input,
|
||||
},
|
||||
});
|
||||
configRefetch();
|
||||
Toast.success({ content: "Updated config" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
statusRefetch();
|
||||
}
|
||||
}
|
||||
|
||||
async function onTempEnable() {
|
||||
const input = {
|
||||
variables: {
|
||||
input: {
|
||||
duration: enableUntilRestart ? undefined : enableDuration,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
if (enableDisable) {
|
||||
await enableDLNA(input);
|
||||
Toast.success({ content: "Enabled DLNA temporarily" });
|
||||
} else {
|
||||
await disableDLNA(input);
|
||||
Toast.success({ content: "Disabled DLNA temporarily" });
|
||||
}
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setEnableDisable(undefined);
|
||||
statusRefetch();
|
||||
}
|
||||
}
|
||||
|
||||
async function onAllowTempIP() {
|
||||
if (!tempIP) {
|
||||
return;
|
||||
}
|
||||
|
||||
const input = {
|
||||
variables: {
|
||||
input: {
|
||||
duration: enableUntilRestart ? undefined : enableDuration,
|
||||
address: tempIP,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await addTempDLANIP(input);
|
||||
Toast.success({ content: "Allowed IP temporarily" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setTempIP(undefined);
|
||||
statusRefetch();
|
||||
}
|
||||
}
|
||||
|
||||
async function onDisallowTempIP(address: string) {
|
||||
const input = {
|
||||
variables: {
|
||||
input: {
|
||||
address,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
await removeTempDLNAIP(input);
|
||||
Toast.success({ content: "Disallowed IP" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
statusRefetch();
|
||||
}
|
||||
}
|
||||
|
||||
function renderDeadline(until?: string) {
|
||||
if (until) {
|
||||
const deadline = new Date(until);
|
||||
return `until ${deadline.toLocaleString()}`;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function renderStatus() {
|
||||
if (!statusData) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const { dlnaStatus } = statusData;
|
||||
const runningText = dlnaStatus.running ? "running" : "not running";
|
||||
|
||||
return `${runningText} ${renderDeadline(dlnaStatus.until)}`;
|
||||
}
|
||||
|
||||
function renderEnableButton() {
|
||||
if (!data?.configuration.dlna) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if enabled by default, then show the disable temporarily
|
||||
// if disabled by default, then show enable temporarily
|
||||
if (data?.configuration.dlna.enabled) {
|
||||
return (
|
||||
<Button onClick={() => setEnableDisable(false)} className="mr-1">
|
||||
Disable temporarily...
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={() => setEnableDisable(true)} className="mr-1">
|
||||
Enable temporarily...
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function canCancel() {
|
||||
if (!statusData || !data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { dlnaStatus } = statusData;
|
||||
const { enabled } = data.configuration.dlna;
|
||||
|
||||
return dlnaStatus.until || dlnaStatus.running !== enabled;
|
||||
}
|
||||
|
||||
async function cancelTempBehaviour() {
|
||||
if (!canCancel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const running = statusData?.dlnaStatus.running;
|
||||
|
||||
const input = {
|
||||
variables: {
|
||||
input: {},
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
if (!running) {
|
||||
await enableDLNA(input);
|
||||
} else {
|
||||
await disableDLNA(input);
|
||||
}
|
||||
Toast.success({ content: "Successfully cancelled temporary behaviour" });
|
||||
} catch (e) {
|
||||
Toast.error(e);
|
||||
} finally {
|
||||
setEnableDisable(undefined);
|
||||
statusRefetch();
|
||||
}
|
||||
}
|
||||
|
||||
function renderTempCancelButton() {
|
||||
if (!canCancel()) {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={() => cancelTempBehaviour()} variant="danger">
|
||||
Cancel temporary behaviour
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTempEnableDialog() {
|
||||
const text: string = enableDisable ? "enable" : "disable";
|
||||
const capitalised = `${text[0].toUpperCase()}${text.slice(1)}`;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
show={enableDisable !== undefined}
|
||||
header={capitalised}
|
||||
icon="clock"
|
||||
accept={{
|
||||
text: capitalised,
|
||||
variant: "primary",
|
||||
onClick: onTempEnable,
|
||||
}}
|
||||
cancel={{
|
||||
onClick: () => setEnableDisable(undefined),
|
||||
variant: "secondary",
|
||||
}}
|
||||
>
|
||||
<h4>{capitalised} temporarily</h4>
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
checked={enableUntilRestart}
|
||||
label="until restart"
|
||||
onChange={() => setEnableUntilRestart(!enableUntilRestart)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group id="temp-enable-duration">
|
||||
<DurationInput
|
||||
numericValue={enableDuration ?? 0}
|
||||
onValueChange={(v) => setEnableDuration(v ?? 0)}
|
||||
disabled={enableUntilRestart}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Duration to {text} for - in minutes.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTempWhitelistDialog() {
|
||||
return (
|
||||
<Modal
|
||||
show={tempIP !== undefined}
|
||||
header={`Allow ${tempIP}`}
|
||||
icon="clock"
|
||||
accept={{
|
||||
text: "Allow",
|
||||
variant: "primary",
|
||||
onClick: onAllowTempIP,
|
||||
}}
|
||||
cancel={{
|
||||
onClick: () => setTempIP(undefined),
|
||||
variant: "secondary",
|
||||
}}
|
||||
>
|
||||
<h4>{`Allow ${tempIP} temporarily`}</h4>
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
checked={enableUntilRestart}
|
||||
label="until restart"
|
||||
onChange={() => setEnableUntilRestart(!enableUntilRestart)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group id="temp-enable-duration">
|
||||
<DurationInput
|
||||
numericValue={enableDuration ?? 0}
|
||||
onValueChange={(v) => setEnableDuration(v ?? 0)}
|
||||
disabled={enableUntilRestart}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Duration to allow for - in minutes.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function renderAllowedIPs() {
|
||||
if (!statusData || statusData.dlnaStatus.allowedIPAddresses.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { allowedIPAddresses } = statusData.dlnaStatus;
|
||||
return (
|
||||
<Form.Group>
|
||||
<h6>Allowed IP addresses</h6>
|
||||
|
||||
<ul className="addresses">
|
||||
{allowedIPAddresses.map((a) => (
|
||||
<li key={a.ipAddress}>
|
||||
<div className="address">
|
||||
<code>{a.ipAddress}</code>
|
||||
<br />
|
||||
<span className="deadline">{renderDeadline(a.until)}</span>
|
||||
</div>
|
||||
|
||||
<div className="buttons">
|
||||
<Button
|
||||
size="sm"
|
||||
title="Disallow"
|
||||
variant="danger"
|
||||
onClick={() => onDisallowTempIP(a.ipAddress)}
|
||||
>
|
||||
<Icon icon="times" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
function renderRecentIPs() {
|
||||
if (!statusData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { recentIPAddresses } = statusData.dlnaStatus;
|
||||
return (
|
||||
<ul className="addresses">
|
||||
{recentIPAddresses.map((a) => (
|
||||
<li key={a}>
|
||||
<div className="address">
|
||||
<code>{a}</code>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
size="sm"
|
||||
title="Allow temporarily"
|
||||
onClick={() => setTempIP(a)}
|
||||
>
|
||||
<Icon icon="user-clock" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<div className="address">
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
value={ipEntry}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setIPEntry(e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="buttons">
|
||||
<Button
|
||||
size="sm"
|
||||
title="Allow temporarily"
|
||||
onClick={() => setTempIP(ipEntry)}
|
||||
disabled={!ipEntry}
|
||||
>
|
||||
<Icon icon="user-clock" />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
const DLNASettingsForm: React.FC = () => {
|
||||
const {
|
||||
handleSubmit,
|
||||
values,
|
||||
setFieldValue,
|
||||
dirty,
|
||||
} = useFormikContext<IConfigValues>();
|
||||
|
||||
return (
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
<Prompt
|
||||
when={dirty}
|
||||
message="Unsaved changes. Are you sure you want to leave?"
|
||||
/>
|
||||
|
||||
<Form.Group>
|
||||
<h5>Settings</h5>
|
||||
<Form.Group>
|
||||
<Form.Label>Server Display Name</Form.Label>
|
||||
<Form.Control
|
||||
className="text-input server-name"
|
||||
value={values.serverName}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setFieldValue("serverName", e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Display name for the DLNA server. Defaults to <code>stash</code>{" "}
|
||||
if empty.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Check
|
||||
checked={values.enabled}
|
||||
label="Enabled by default"
|
||||
onChange={() => setFieldValue("enabled", !values.enabled)}
|
||||
/>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<h6>Interfaces</h6>
|
||||
<StringListInput
|
||||
value={values.interfaces}
|
||||
setValue={(value) => setFieldValue("interfaces", value)}
|
||||
defaultNewValue=""
|
||||
className="interfaces-input"
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Interfaces to expose DLNA server on. An empty list results in
|
||||
running on all interfaces. Requires DLNA restart after changing.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<h6>Default IP Whitelist</h6>
|
||||
<StringListInput
|
||||
value={values.whitelistedIPs}
|
||||
setValue={(value) => setFieldValue("whitelistedIPs", value)}
|
||||
defaultNewValue="*"
|
||||
className="ip-whitelist-input"
|
||||
/>
|
||||
<Form.Text className="text-muted">
|
||||
Default IP addresses allow to access DLNA. Use <code>*</code> to
|
||||
allow all IP addresses.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
</Form.Group>
|
||||
|
||||
<hr />
|
||||
|
||||
<Button variant="primary" type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="settings-dlna">
|
||||
{renderTempEnableDialog()}
|
||||
{renderTempWhitelistDialog()}
|
||||
|
||||
<h4>DLNA</h4>
|
||||
|
||||
<Form.Group>
|
||||
<h5>Status: {renderStatus()}</h5>
|
||||
</Form.Group>
|
||||
|
||||
<Form.Group>
|
||||
<h5>Actions</h5>
|
||||
|
||||
<Form.Group>
|
||||
{renderEnableButton()}
|
||||
{renderTempCancelButton()}
|
||||
</Form.Group>
|
||||
|
||||
{renderAllowedIPs()}
|
||||
|
||||
<Form.Group>
|
||||
<h6>Recent IP addresses</h6>
|
||||
<Form.Group>{renderRecentIPs()}</Form.Group>
|
||||
<Form.Group>
|
||||
<Button onClick={() => statusRefetch()}>Refresh</Button>
|
||||
</Form.Group>
|
||||
</Form.Group>
|
||||
</Form.Group>
|
||||
|
||||
<hr />
|
||||
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
validationSchema={schema}
|
||||
onSubmit={(values) => onSave(values)}
|
||||
enableReinitialize
|
||||
>
|
||||
<DLNASettingsForm />
|
||||
</Formik>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -70,3 +70,46 @@
|
|||
list-style: none;
|
||||
}
|
||||
}
|
||||
|
||||
#temp-enable-duration .duration-control:disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#settings-dlna {
|
||||
.ip-whitelist-input,
|
||||
.interfaces-input {
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
.server-name {
|
||||
width: 24em;
|
||||
}
|
||||
|
||||
.addresses {
|
||||
list-style-type: none;
|
||||
padding-inline-start: 0;
|
||||
|
||||
li {
|
||||
display: flex;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.address {
|
||||
display: inline-block;
|
||||
width: 12em;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.deadline {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
code {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ interface IModal {
|
|||
dialogClassName?: string;
|
||||
}
|
||||
|
||||
const defaultOnHide = () => {};
|
||||
|
||||
const ModalComponent: React.FC<IModal> = ({
|
||||
children,
|
||||
show,
|
||||
|
|
@ -37,7 +39,7 @@ const ModalComponent: React.FC<IModal> = ({
|
|||
}) => (
|
||||
<Modal
|
||||
keyboard={false}
|
||||
onHide={onHide}
|
||||
onHide={onHide ?? defaultOnHide}
|
||||
show={show}
|
||||
dialogClassName={dialogClassName}
|
||||
{...modalProps}
|
||||
|
|
|
|||
60
ui/v2.5/src/components/Shared/StringListInput.tsx
Normal file
60
ui/v2.5/src/components/Shared/StringListInput.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import React from "react";
|
||||
import { Button, Form, InputGroup } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
||||
interface IStringListInputProps {
|
||||
value: string[];
|
||||
setValue: (value: string[]) => void;
|
||||
defaultNewValue?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const StringListInput: React.FC<IStringListInputProps> = (props) => {
|
||||
function valueChanged(idx: number, value: string) {
|
||||
const newValues = props.value.map((v, i) => {
|
||||
const ret = idx !== i ? v : value;
|
||||
return ret;
|
||||
});
|
||||
props.setValue(newValues);
|
||||
}
|
||||
|
||||
function removeValue(idx: number) {
|
||||
const newValues = props.value.filter((_v, i) => i !== idx);
|
||||
|
||||
props.setValue(newValues);
|
||||
}
|
||||
|
||||
function addValue() {
|
||||
const newValues = props.value.concat(props.defaultNewValue ?? "");
|
||||
|
||||
props.setValue(newValues);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Group>
|
||||
{props.value &&
|
||||
props.value.map((v, i) => (
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
<InputGroup className={props.className} key={i}>
|
||||
<Form.Control
|
||||
className="text-input"
|
||||
value={v}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
valueChanged(i, e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<Button variant="danger" onClick={() => removeValue(i)}>
|
||||
<Icon icon="minus" />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
))}
|
||||
</Form.Group>
|
||||
<Button className="minimal" onClick={() => addValue()}>
|
||||
<Icon icon="plus" />
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -701,6 +701,20 @@ export const useGenerateAPIKey = () =>
|
|||
update: deleteCache([GQL.ConfigurationDocument]),
|
||||
});
|
||||
|
||||
export const useConfigureDLNA = () =>
|
||||
GQL.useConfigureDlnaMutation({
|
||||
refetchQueries: getQueryNames([GQL.ConfigurationDocument]),
|
||||
update: deleteCache([GQL.ConfigurationDocument]),
|
||||
});
|
||||
|
||||
export const useEnableDLNA = () => GQL.useEnableDlnaMutation();
|
||||
|
||||
export const useDisableDLNA = () => GQL.useDisableDlnaMutation();
|
||||
|
||||
export const useAddTempDLNAIP = () => GQL.useAddTempDlnaipMutation();
|
||||
|
||||
export const useRemoveTempDLNAIP = () => GQL.useRemoveTempDlnaipMutation();
|
||||
|
||||
export const useMetadataUpdate = () => GQL.useMetadataUpdateSubscription();
|
||||
|
||||
export const useLoggingSubscribe = () => GQL.useLoggingSubscribeSubscription();
|
||||
|
|
@ -731,6 +745,11 @@ export const mutateStopJob = () =>
|
|||
mutation: GQL.StopJobDocument,
|
||||
});
|
||||
|
||||
export const useDLNAStatus = () =>
|
||||
GQL.useDlnaStatusQuery({
|
||||
fetchPolicy: "no-cache",
|
||||
});
|
||||
|
||||
export const queryScrapeFreeones = (performerName: string) =>
|
||||
client.query<GQL.ScrapeFreeonesQuery>({
|
||||
query: GQL.ScrapeFreeonesDocument,
|
||||
|
|
|
|||
24
vendor/github.com/anacrolix/dms/LICENSE
generated
vendored
Normal file
24
vendor/github.com/anacrolix/dms/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
Copyright (c) 2012, Matt Joiner <anacrolix@gmail.com>.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the <organization> nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
102
vendor/github.com/anacrolix/dms/dlna/dlna.go
generated
vendored
Normal file
102
vendor/github.com/anacrolix/dms/dlna/dlna.go
generated
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package dlna
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
TimeSeekRangeDomain = "TimeSeekRange.dlna.org"
|
||||
ContentFeaturesDomain = "contentFeatures.dlna.org"
|
||||
TransferModeDomain = "transferMode.dlna.org"
|
||||
)
|
||||
|
||||
type ContentFeatures struct {
|
||||
ProfileName string
|
||||
SupportTimeSeek bool
|
||||
SupportRange bool
|
||||
// Play speeds, DLNA.ORG_PS would go here if supported.
|
||||
Transcoded bool
|
||||
}
|
||||
|
||||
func BinaryInt(b bool) uint {
|
||||
if b {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// flags are in hex. trailing 24 zeroes, 26 are after the space
|
||||
// "DLNA.ORG_OP=" time-seek-range-supp bytes-range-header-supp
|
||||
func (cf ContentFeatures) String() (ret string) {
|
||||
//DLNA.ORG_PN=[a-zA-Z0-9_]*
|
||||
params := make([]string, 0, 2)
|
||||
if cf.ProfileName != "" {
|
||||
params = append(params, "DLNA.ORG_PN="+cf.ProfileName)
|
||||
}
|
||||
params = append(params, fmt.Sprintf(
|
||||
"DLNA.ORG_OP=%b%b;DLNA.ORG_CI=%b",
|
||||
BinaryInt(cf.SupportTimeSeek),
|
||||
BinaryInt(cf.SupportRange),
|
||||
BinaryInt(cf.Transcoded)))
|
||||
return strings.Join(params, ";")
|
||||
}
|
||||
|
||||
func ParseNPTTime(s string) (time.Duration, error) {
|
||||
var h, m, sec, ms time.Duration
|
||||
n, err := fmt.Sscanf(s, "%d:%2d:%2d.%3d", &h, &m, &sec, &ms)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
if n < 3 {
|
||||
return -1, fmt.Errorf("invalid npt time: %s", s)
|
||||
}
|
||||
ret := time.Duration(h) * time.Hour
|
||||
ret += time.Duration(m) * time.Minute
|
||||
ret += sec * time.Second
|
||||
ret += ms * time.Millisecond
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func FormatNPTTime(npt time.Duration) string {
|
||||
npt /= time.Millisecond
|
||||
ms := npt % 1000
|
||||
npt /= 1000
|
||||
s := npt % 60
|
||||
npt /= 60
|
||||
m := npt % 60
|
||||
npt /= 60
|
||||
h := npt
|
||||
return fmt.Sprintf("%02d:%02d:%02d.%03d", h, m, s, ms)
|
||||
}
|
||||
|
||||
type NPTRange struct {
|
||||
Start, End time.Duration
|
||||
}
|
||||
|
||||
func ParseNPTRange(s string) (ret NPTRange, err error) {
|
||||
ss := strings.SplitN(s, "-", 2)
|
||||
if ss[0] != "" {
|
||||
ret.Start, err = ParseNPTTime(ss[0])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if ss[1] != "" {
|
||||
ret.End, err = ParseNPTTime(ss[1])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (me NPTRange) String() (ret string) {
|
||||
ret = me.Start.String() + "-"
|
||||
if me.End >= 0 {
|
||||
ret += me.End.String()
|
||||
}
|
||||
return
|
||||
}
|
||||
68
vendor/github.com/anacrolix/dms/soap/soap.go
generated
vendored
Normal file
68
vendor/github.com/anacrolix/dms/soap/soap.go
generated
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package soap
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
const (
|
||||
EncodingStyle = "http://schemas.xmlsoap.org/soap/encoding/"
|
||||
EnvelopeNS = "http://schemas.xmlsoap.org/soap/envelope/"
|
||||
)
|
||||
|
||||
type Arg struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
XMLName xml.Name
|
||||
Args []Arg
|
||||
}
|
||||
|
||||
type Body struct {
|
||||
Action []byte `xml:",innerxml"`
|
||||
}
|
||||
|
||||
type UPnPError struct {
|
||||
XMLName xml.Name `xml:"urn:schemas-upnp-org:control-1-0 UPnPError"`
|
||||
Code uint `xml:"errorCode"`
|
||||
Desc string `xml:"errorDescription"`
|
||||
}
|
||||
|
||||
type FaultDetail struct {
|
||||
XMLName xml.Name `xml:"detail"`
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type Fault struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault"`
|
||||
FaultCode string `xml:"faultcode"`
|
||||
FaultString string `xml:"faultstring"`
|
||||
Detail FaultDetail `xml:"detail"`
|
||||
}
|
||||
|
||||
func NewFault(s string, detail interface{}) *Fault {
|
||||
return &Fault{
|
||||
FaultCode: EnvelopeNS + ":Client",
|
||||
FaultString: s,
|
||||
Detail: FaultDetail{
|
||||
Data: detail,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type Envelope struct {
|
||||
XMLName xml.Name `xml:"http://schemas.xmlsoap.org/soap/envelope/ Envelope"`
|
||||
EncodingStyle string `xml:"encodingStyle,attr"`
|
||||
Body Body `xml:"http://schemas.xmlsoap.org/soap/envelope/ Body"`
|
||||
}
|
||||
|
||||
/* XML marshalling of nested namespaces is broken.
|
||||
|
||||
func NewEnvelope(action []byte) Envelope {
|
||||
return Envelope{
|
||||
EncodingStyle: EncodingStyle,
|
||||
Body: Body{action},
|
||||
}
|
||||
}
|
||||
*/
|
||||
330
vendor/github.com/anacrolix/dms/ssdp/ssdp.go
generated
vendored
Normal file
330
vendor/github.com/anacrolix/dms/ssdp/ssdp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
package ssdp
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
const (
|
||||
AddrString = "239.255.255.250:1900"
|
||||
rootDevice = "upnp:rootdevice"
|
||||
aliveNTS = "ssdp:alive"
|
||||
byebyeNTS = "ssdp:byebye"
|
||||
)
|
||||
|
||||
var (
|
||||
NetAddr *net.UDPAddr
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
NetAddr, err = net.ResolveUDPAddr("udp4", AddrString)
|
||||
if err != nil {
|
||||
log.Panicf("Could not resolve %s: %s", AddrString, err)
|
||||
}
|
||||
}
|
||||
|
||||
type badStringError struct {
|
||||
what string
|
||||
str string
|
||||
}
|
||||
|
||||
func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) }
|
||||
|
||||
func ReadRequest(b *bufio.Reader) (req *http.Request, err error) {
|
||||
tp := textproto.NewReader(b)
|
||||
var s string
|
||||
if s, err = tp.ReadLine(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err == io.EOF {
|
||||
err = io.ErrUnexpectedEOF
|
||||
}
|
||||
}()
|
||||
|
||||
var f []string
|
||||
// TODO a split that only allows N values?
|
||||
if f = strings.SplitN(s, " ", 3); len(f) < 3 {
|
||||
return nil, &badStringError{"malformed request line", s}
|
||||
}
|
||||
if f[1] != "*" {
|
||||
return nil, &badStringError{"bad URL request", f[1]}
|
||||
}
|
||||
req = &http.Request{
|
||||
Method: f[0],
|
||||
}
|
||||
var ok bool
|
||||
if req.ProtoMajor, req.ProtoMinor, ok = http.ParseHTTPVersion(strings.TrimSpace(f[2])); !ok {
|
||||
return nil, &badStringError{"malformed HTTP version", f[2]}
|
||||
}
|
||||
|
||||
mimeHeader, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = http.Header(mimeHeader)
|
||||
return
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
conn *net.UDPConn
|
||||
Interface net.Interface
|
||||
Server string
|
||||
Services []string
|
||||
Devices []string
|
||||
Location func(net.IP) string
|
||||
UUID string
|
||||
NotifyInterval time.Duration
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
func makeConn(ifi net.Interface) (ret *net.UDPConn, err error) {
|
||||
ret, err = net.ListenMulticastUDP("udp", &ifi, NetAddr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
p := ipv4.NewPacketConn(ret)
|
||||
if err := p.SetMulticastTTL(2); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
if err := p.SetMulticastLoopback(true); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (me *Server) serve() {
|
||||
for {
|
||||
b := make([]byte, me.Interface.MTU)
|
||||
n, addr, err := me.conn.ReadFromUDP(b)
|
||||
select {
|
||||
case <-me.closed:
|
||||
return
|
||||
default:
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("error reading from UDP socket: %s", err)
|
||||
break
|
||||
}
|
||||
go me.handle(b[:n], addr)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) Init() (err error) {
|
||||
me.closed = make(chan struct{})
|
||||
me.conn, err = makeConn(me.Interface)
|
||||
return
|
||||
}
|
||||
|
||||
func (me *Server) Close() {
|
||||
close(me.closed)
|
||||
me.sendByeBye()
|
||||
me.conn.Close()
|
||||
}
|
||||
|
||||
func (me *Server) Serve() (err error) {
|
||||
go me.serve()
|
||||
for {
|
||||
addrs, err := me.Interface.Addrs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
ip := func() net.IP {
|
||||
switch val := addr.(type) {
|
||||
case *net.IPNet:
|
||||
return val.IP
|
||||
case *net.IPAddr:
|
||||
return val.IP
|
||||
}
|
||||
panic(fmt.Sprint("unexpected addr type:", addr))
|
||||
}()
|
||||
extraHdrs := [][2]string{
|
||||
{"CACHE-CONTROL", fmt.Sprintf("max-age=%d", 5*me.NotifyInterval/2/time.Second)},
|
||||
{"LOCATION", me.Location(ip)},
|
||||
}
|
||||
me.notifyAll(aliveNTS, extraHdrs)
|
||||
}
|
||||
time.Sleep(me.NotifyInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) usnFromTarget(target string) string {
|
||||
if target == me.UUID {
|
||||
return target
|
||||
}
|
||||
return me.UUID + "::" + target
|
||||
}
|
||||
|
||||
func (me *Server) makeNotifyMessage(target, nts string, extraHdrs [][2]string) []byte {
|
||||
lines := [...][2]string{
|
||||
{"HOST", AddrString},
|
||||
{"NT", target},
|
||||
{"NTS", nts},
|
||||
{"SERVER", me.Server},
|
||||
{"USN", me.usnFromTarget(target)},
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
fmt.Fprint(buf, "NOTIFY * HTTP/1.1\r\n")
|
||||
writeHdr := func(keyValue [2]string) {
|
||||
fmt.Fprintf(buf, "%s: %s\r\n", keyValue[0], keyValue[1])
|
||||
}
|
||||
for _, pair := range lines {
|
||||
writeHdr(pair)
|
||||
}
|
||||
for _, pair := range extraHdrs {
|
||||
writeHdr(pair)
|
||||
}
|
||||
fmt.Fprint(buf, "\r\n")
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (me *Server) send(buf []byte, addr *net.UDPAddr) {
|
||||
if n, err := me.conn.WriteToUDP(buf, addr); err != nil {
|
||||
log.Printf("error writing to UDP socket: %s", err)
|
||||
} else if n != len(buf) {
|
||||
log.Printf("short write: %d/%d bytes", n, len(buf))
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) delayedSend(delay time.Duration, buf []byte, addr *net.UDPAddr) {
|
||||
go func() {
|
||||
select {
|
||||
case <-time.After(delay):
|
||||
me.send(buf, addr)
|
||||
case <-me.closed:
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (me *Server) log(args ...interface{}) {
|
||||
args = append([]interface{}{me.Interface.Name + ":"}, args...)
|
||||
log.Print(args...)
|
||||
}
|
||||
|
||||
func (me *Server) sendByeBye() {
|
||||
for _, type_ := range me.allTypes() {
|
||||
buf := me.makeNotifyMessage(type_, byebyeNTS, nil)
|
||||
me.send(buf, NetAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) notifyAll(nts string, extraHdrs [][2]string) {
|
||||
for _, type_ := range me.allTypes() {
|
||||
buf := me.makeNotifyMessage(type_, nts, extraHdrs)
|
||||
delay := time.Duration(rand.Int63n(int64(100 * time.Millisecond)))
|
||||
me.delayedSend(delay, buf, NetAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) allTypes() (ret []string) {
|
||||
for _, a := range [][]string{
|
||||
{rootDevice, me.UUID},
|
||||
me.Devices,
|
||||
me.Services,
|
||||
} {
|
||||
ret = append(ret, a...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (me *Server) handle(buf []byte, sender *net.UDPAddr) {
|
||||
req, err := ReadRequest(bufio.NewReader(bytes.NewReader(buf)))
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return
|
||||
}
|
||||
if req.Method != "M-SEARCH" || req.Header.Get("man") != `"ssdp:discover"` {
|
||||
return
|
||||
}
|
||||
var mx uint
|
||||
if req.Header.Get("Host") == AddrString {
|
||||
mxHeader := req.Header.Get("mx")
|
||||
i, err := strconv.ParseUint(mxHeader, 0, 0)
|
||||
if err != nil {
|
||||
log.Printf("Invalid mx header %q: %s", mxHeader, err)
|
||||
return
|
||||
}
|
||||
mx = uint(i)
|
||||
} else {
|
||||
mx = 1
|
||||
}
|
||||
types := func(st string) []string {
|
||||
if st == "ssdp:all" {
|
||||
return me.allTypes()
|
||||
}
|
||||
for _, t := range me.allTypes() {
|
||||
if t == st {
|
||||
return []string{t}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}(req.Header.Get("st"))
|
||||
for _, ip := range func() (ret []net.IP) {
|
||||
addrs, err := me.Interface.Addrs()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if ip, ok := func() (net.IP, bool) {
|
||||
switch data := addr.(type) {
|
||||
case *net.IPNet:
|
||||
if data.Contains(sender.IP) {
|
||||
return data.IP, true
|
||||
}
|
||||
return nil, false
|
||||
case *net.IPAddr:
|
||||
return data.IP, true
|
||||
}
|
||||
panic(addr)
|
||||
}(); ok {
|
||||
ret = append(ret, ip)
|
||||
}
|
||||
}
|
||||
return
|
||||
}() {
|
||||
for _, type_ := range types {
|
||||
resp := me.makeResponse(ip, type_, req)
|
||||
delay := time.Duration(rand.Int63n(int64(time.Second) * int64(mx)))
|
||||
me.delayedSend(delay, resp, sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (me *Server) makeResponse(ip net.IP, targ string, req *http.Request) (ret []byte) {
|
||||
resp := &http.Response{
|
||||
StatusCode: 200,
|
||||
ProtoMajor: 1,
|
||||
ProtoMinor: 1,
|
||||
Header: make(http.Header),
|
||||
Request: req,
|
||||
}
|
||||
for _, pair := range [...][2]string{
|
||||
{"CACHE-CONTROL", fmt.Sprintf("max-age=%d", 5*me.NotifyInterval/2/time.Second)},
|
||||
{"EXT", ""},
|
||||
{"LOCATION", me.Location(ip)},
|
||||
{"SERVER", me.Server},
|
||||
{"ST", targ},
|
||||
{"USN", me.usnFromTarget(targ)},
|
||||
} {
|
||||
resp.Header.Set(pair[0], pair[1])
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
if err := resp.Write(buf); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
91
vendor/github.com/anacrolix/dms/upnp/eventing.go
generated
vendored
Normal file
91
vendor/github.com/anacrolix/dms/upnp/eventing.go
generated
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package upnp
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: Why use namespace prefixes in PropertySet et al? Because the spec
|
||||
// uses them, and I believe the Golang standard library XML spec implementers
|
||||
// incorrectly assume that you can get away with just xmlns="".
|
||||
|
||||
// propertyset is the root element sent in an event callback.
|
||||
type PropertySet struct {
|
||||
XMLName struct{} `xml:"e:propertyset"`
|
||||
Properties []Property
|
||||
// This should be set to `"urn:schemas-upnp-org:event-1-0"`.
|
||||
Space string `xml:"xmlns:e,attr"`
|
||||
}
|
||||
|
||||
// propertys provide namespacing to the contained variables.
|
||||
type Property struct {
|
||||
XMLName struct{} `xml:"e:property"`
|
||||
Variable Variable
|
||||
}
|
||||
|
||||
// Represents an evented state variable that has sendEvents="yes" in its
|
||||
// service spec.
|
||||
type Variable struct {
|
||||
XMLName xml.Name
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type subscriber struct {
|
||||
sid string
|
||||
nextSeq uint32 // 0 for initial event, wraps from Uint32Max to 1.
|
||||
urls []*url.URL
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
// Intended to eventually be an embeddable implementation for managing
|
||||
// eventing for a service. Not complete.
|
||||
type Eventing struct {
|
||||
subscribers map[string]*subscriber
|
||||
}
|
||||
|
||||
func (me *Eventing) Subscribe(callback []*url.URL, timeoutSeconds int) (sid string, actualTimeout int, err error) {
|
||||
var uuid [16]byte
|
||||
io.ReadFull(rand.Reader, uuid[:])
|
||||
sid = FormatUUID(uuid[:])
|
||||
if _, ok := me.subscribers[sid]; ok {
|
||||
err = fmt.Errorf("already subscribed: %s", sid)
|
||||
return
|
||||
}
|
||||
ssr := &subscriber{
|
||||
sid: sid,
|
||||
urls: callback,
|
||||
expiry: time.Now().Add(time.Duration(timeoutSeconds) * time.Second),
|
||||
}
|
||||
if me.subscribers == nil {
|
||||
me.subscribers = make(map[string]*subscriber)
|
||||
}
|
||||
me.subscribers[sid] = ssr
|
||||
actualTimeout = int(ssr.expiry.Sub(time.Now()) / time.Second)
|
||||
return
|
||||
}
|
||||
|
||||
func (me *Eventing) Unsubscribe(sid string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var callbackURLRegexp = regexp.MustCompile("<(.*?)>")
|
||||
|
||||
// Parse the CALLBACK HTTP header in an event subscription request. See UPnP
|
||||
// Device Architecture 4.1.2.
|
||||
func ParseCallbackURLs(callback string) (ret []*url.URL) {
|
||||
for _, match := range callbackURLRegexp.FindAllStringSubmatch(callback, -1) {
|
||||
_url, err := url.Parse(match[1])
|
||||
if err != nil {
|
||||
log.Printf("bad callback url: %q", match[1])
|
||||
continue
|
||||
}
|
||||
ret = append(ret, _url)
|
||||
}
|
||||
return
|
||||
}
|
||||
162
vendor/github.com/anacrolix/dms/upnp/upnp.go
generated
vendored
Normal file
162
vendor/github.com/anacrolix/dms/upnp/upnp.go
generated
vendored
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
package upnp
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var serviceURNRegexp *regexp.Regexp = regexp.MustCompile(`^urn:schemas-upnp-org:service:(\w+):(\d+)$`)
|
||||
|
||||
type ServiceURN struct {
|
||||
Type string
|
||||
Version uint64
|
||||
}
|
||||
|
||||
func (me ServiceURN) String() string {
|
||||
return fmt.Sprintf("urn:schemas-upnp-org:service:%s:%d", me.Type, me.Version)
|
||||
}
|
||||
|
||||
func ParseServiceType(s string) (ret ServiceURN, err error) {
|
||||
matches := serviceURNRegexp.FindStringSubmatch(s)
|
||||
if matches == nil {
|
||||
err = errors.New(s)
|
||||
return
|
||||
}
|
||||
if len(matches) != 3 {
|
||||
log.Panicf("Invalid serviceURNRegexp ?")
|
||||
}
|
||||
ret.Type = matches[1]
|
||||
ret.Version, err = strconv.ParseUint(matches[2], 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
type SoapAction struct {
|
||||
ServiceURN
|
||||
Action string
|
||||
}
|
||||
|
||||
func ParseActionHTTPHeader(s string) (ret SoapAction, err error) {
|
||||
if len(s) < 3 {
|
||||
return
|
||||
}
|
||||
if s[0] != '"' || s[len(s)-1] != '"' {
|
||||
return
|
||||
}
|
||||
s = s[1 : len(s)-1]
|
||||
hashIndex := strings.LastIndex(s, "#")
|
||||
if hashIndex == -1 {
|
||||
return
|
||||
}
|
||||
ret.Action = s[hashIndex+1:]
|
||||
ret.ServiceURN, err = ParseServiceType(s[:hashIndex])
|
||||
return
|
||||
}
|
||||
|
||||
type SpecVersion struct {
|
||||
Major int `xml:"major"`
|
||||
Minor int `xml:"minor"`
|
||||
}
|
||||
|
||||
type Icon struct {
|
||||
Mimetype string `xml:"mimetype"`
|
||||
Width int `xml:"width"`
|
||||
Height int `xml:"height"`
|
||||
Depth int `xml:"depth"`
|
||||
URL string `xml:"url"`
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
XMLName xml.Name `xml:"service"`
|
||||
ServiceType string `xml:"serviceType"`
|
||||
ServiceId string `xml:"serviceId"`
|
||||
SCPDURL string
|
||||
ControlURL string `xml:"controlURL"`
|
||||
EventSubURL string `xml:"eventSubURL"`
|
||||
}
|
||||
|
||||
type Device struct {
|
||||
DeviceType string `xml:"deviceType"`
|
||||
FriendlyName string `xml:"friendlyName"`
|
||||
Manufacturer string `xml:"manufacturer"`
|
||||
ModelName string `xml:"modelName"`
|
||||
UDN string
|
||||
IconList []Icon `xml:"iconList>icon"`
|
||||
ServiceList []Service `xml:"serviceList>service"`
|
||||
}
|
||||
|
||||
type DeviceDesc struct {
|
||||
XMLName xml.Name `xml:"urn:schemas-upnp-org:device-1-0 root"`
|
||||
SpecVersion SpecVersion `xml:"specVersion"`
|
||||
Device Device `xml:"device"`
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
XMLName xml.Name `xml:"urn:schemas-upnp-org:control-1-0 UPnPError"`
|
||||
Code uint `xml:"errorCode"`
|
||||
Desc string `xml:"errorDescription"`
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("%d %s", e.Code, e.Desc)
|
||||
}
|
||||
|
||||
const (
|
||||
InvalidActionErrorCode = 401
|
||||
ActionFailedErrorCode = 501
|
||||
ArgumentValueInvalidErrorCode = 600
|
||||
)
|
||||
|
||||
var (
|
||||
InvalidActionError = Errorf(401, "Invalid Action")
|
||||
ArgumentValueInvalidError = Errorf(600, "The argument value is invalid")
|
||||
)
|
||||
|
||||
// Errorf creates an UPNP error from the given code and description
|
||||
func Errorf(code uint, tpl string, args ...interface{}) *Error {
|
||||
return &Error{Code: code, Desc: fmt.Sprintf(tpl, args...)}
|
||||
}
|
||||
|
||||
// ConvertError converts any error to an UPNP error
|
||||
func ConvertError(err error) *Error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if e, ok := err.(*Error); ok {
|
||||
return e
|
||||
}
|
||||
return Errorf(ActionFailedErrorCode, err.Error())
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
Name string
|
||||
Arguments []Argument
|
||||
}
|
||||
|
||||
type Argument struct {
|
||||
Name string
|
||||
Direction string
|
||||
RelatedStateVar string
|
||||
}
|
||||
|
||||
type SCPD struct {
|
||||
XMLName xml.Name `xml:"urn:schemas-upnp-org:service-1-0 scpd"`
|
||||
SpecVersion SpecVersion `xml:"specVersion"`
|
||||
ActionList []Action `xml:"actionList>action"`
|
||||
ServiceStateTable []StateVariable `xml:"serviceStateTable>stateVariable"`
|
||||
}
|
||||
|
||||
type StateVariable struct {
|
||||
SendEvents string `xml:"sendEvents,attr"`
|
||||
Name string `xml:"name"`
|
||||
DataType string `xml:"dataType"`
|
||||
AllowedValues *[]string `xml:"allowedValueList>allowedValue,omitempty"`
|
||||
}
|
||||
|
||||
func FormatUUID(buf []byte) string {
|
||||
return fmt.Sprintf("uuid:%x-%x-%x-%x-%x", buf[:4], buf[4:6], buf[6:8], buf[8:10], buf[10:16])
|
||||
}
|
||||
45
vendor/github.com/anacrolix/dms/upnpav/upnpav.go
generated
vendored
Normal file
45
vendor/github.com/anacrolix/dms/upnpav/upnpav.go
generated
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package upnpav
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
const (
|
||||
NoSuchObjectErrorCode = 701
|
||||
)
|
||||
|
||||
type Resource struct {
|
||||
XMLName xml.Name `xml:"res"`
|
||||
ProtocolInfo string `xml:"protocolInfo,attr"`
|
||||
URL string `xml:",chardata"`
|
||||
Size uint64 `xml:"size,attr,omitempty"`
|
||||
Bitrate uint `xml:"bitrate,attr,omitempty"`
|
||||
Duration string `xml:"duration,attr,omitempty"`
|
||||
Resolution string `xml:"resolution,attr,omitempty"`
|
||||
}
|
||||
|
||||
type Container struct {
|
||||
Object
|
||||
XMLName xml.Name `xml:"container"`
|
||||
ChildCount int `xml:"childCount,attr"`
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
Object
|
||||
XMLName xml.Name `xml:"item"`
|
||||
Res []Resource
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
ID string `xml:"id,attr"`
|
||||
ParentID string `xml:"parentID,attr"`
|
||||
Restricted int `xml:"restricted,attr"` // indicates whether the object is modifiable
|
||||
Class string `xml:"upnp:class"`
|
||||
Icon string `xml:"upnp:icon,omitempty"`
|
||||
Title string `xml:"dc:title"`
|
||||
Artist string `xml:"upnp:artist,omitempty"`
|
||||
Album string `xml:"upnp:album,omitempty"`
|
||||
Genre string `xml:"upnp:genre,omitempty"`
|
||||
AlbumArtURI string `xml:"upnp:albumArtURI,omitempty"`
|
||||
Searchable int `xml:"searchable,attr"`
|
||||
}
|
||||
41
vendor/golang.org/x/net/bpf/asm.go
generated
vendored
Normal file
41
vendor/golang.org/x/net/bpf/asm.go
generated
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Assemble converts insts into raw instructions suitable for loading
|
||||
// into a BPF virtual machine.
|
||||
//
|
||||
// Currently, no optimization is attempted, the assembled program flow
|
||||
// is exactly as provided.
|
||||
func Assemble(insts []Instruction) ([]RawInstruction, error) {
|
||||
ret := make([]RawInstruction, len(insts))
|
||||
var err error
|
||||
for i, inst := range insts {
|
||||
ret[i], err = inst.Assemble()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("assembling instruction %d: %s", i+1, err)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Disassemble attempts to parse raw back into
|
||||
// Instructions. Unrecognized RawInstructions are assumed to be an
|
||||
// extension not implemented by this package, and are passed through
|
||||
// unchanged to the output. The allDecoded value reports whether insts
|
||||
// contains no RawInstructions.
|
||||
func Disassemble(raw []RawInstruction) (insts []Instruction, allDecoded bool) {
|
||||
insts = make([]Instruction, len(raw))
|
||||
allDecoded = true
|
||||
for i, r := range raw {
|
||||
insts[i] = r.Disassemble()
|
||||
if _, ok := insts[i].(RawInstruction); ok {
|
||||
allDecoded = false
|
||||
}
|
||||
}
|
||||
return insts, allDecoded
|
||||
}
|
||||
222
vendor/golang.org/x/net/bpf/constants.go
generated
vendored
Normal file
222
vendor/golang.org/x/net/bpf/constants.go
generated
vendored
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
// A Register is a register of the BPF virtual machine.
|
||||
type Register uint16
|
||||
|
||||
const (
|
||||
// RegA is the accumulator register. RegA is always the
|
||||
// destination register of ALU operations.
|
||||
RegA Register = iota
|
||||
// RegX is the indirection register, used by LoadIndirect
|
||||
// operations.
|
||||
RegX
|
||||
)
|
||||
|
||||
// An ALUOp is an arithmetic or logic operation.
|
||||
type ALUOp uint16
|
||||
|
||||
// ALU binary operation types.
|
||||
const (
|
||||
ALUOpAdd ALUOp = iota << 4
|
||||
ALUOpSub
|
||||
ALUOpMul
|
||||
ALUOpDiv
|
||||
ALUOpOr
|
||||
ALUOpAnd
|
||||
ALUOpShiftLeft
|
||||
ALUOpShiftRight
|
||||
aluOpNeg // Not exported because it's the only unary ALU operation, and gets its own instruction type.
|
||||
ALUOpMod
|
||||
ALUOpXor
|
||||
)
|
||||
|
||||
// A JumpTest is a comparison operator used in conditional jumps.
|
||||
type JumpTest uint16
|
||||
|
||||
// Supported operators for conditional jumps.
|
||||
// K can be RegX for JumpIfX
|
||||
const (
|
||||
// K == A
|
||||
JumpEqual JumpTest = iota
|
||||
// K != A
|
||||
JumpNotEqual
|
||||
// K > A
|
||||
JumpGreaterThan
|
||||
// K < A
|
||||
JumpLessThan
|
||||
// K >= A
|
||||
JumpGreaterOrEqual
|
||||
// K <= A
|
||||
JumpLessOrEqual
|
||||
// K & A != 0
|
||||
JumpBitsSet
|
||||
// K & A == 0
|
||||
JumpBitsNotSet
|
||||
)
|
||||
|
||||
// An Extension is a function call provided by the kernel that
|
||||
// performs advanced operations that are expensive or impossible
|
||||
// within the BPF virtual machine.
|
||||
//
|
||||
// Extensions are only implemented by the Linux kernel.
|
||||
//
|
||||
// TODO: should we prune this list? Some of these extensions seem
|
||||
// either broken or near-impossible to use correctly, whereas other
|
||||
// (len, random, ifindex) are quite useful.
|
||||
type Extension int
|
||||
|
||||
// Extension functions available in the Linux kernel.
|
||||
const (
|
||||
// extOffset is the negative maximum number of instructions used
|
||||
// to load instructions by overloading the K argument.
|
||||
extOffset = -0x1000
|
||||
// ExtLen returns the length of the packet.
|
||||
ExtLen Extension = 1
|
||||
// ExtProto returns the packet's L3 protocol type.
|
||||
ExtProto Extension = 0
|
||||
// ExtType returns the packet's type (skb->pkt_type in the kernel)
|
||||
//
|
||||
// TODO: better documentation. How nice an API do we want to
|
||||
// provide for these esoteric extensions?
|
||||
ExtType Extension = 4
|
||||
// ExtPayloadOffset returns the offset of the packet payload, or
|
||||
// the first protocol header that the kernel does not know how to
|
||||
// parse.
|
||||
ExtPayloadOffset Extension = 52
|
||||
// ExtInterfaceIndex returns the index of the interface on which
|
||||
// the packet was received.
|
||||
ExtInterfaceIndex Extension = 8
|
||||
// ExtNetlinkAttr returns the netlink attribute of type X at
|
||||
// offset A.
|
||||
ExtNetlinkAttr Extension = 12
|
||||
// ExtNetlinkAttrNested returns the nested netlink attribute of
|
||||
// type X at offset A.
|
||||
ExtNetlinkAttrNested Extension = 16
|
||||
// ExtMark returns the packet's mark value.
|
||||
ExtMark Extension = 20
|
||||
// ExtQueue returns the packet's assigned hardware queue.
|
||||
ExtQueue Extension = 24
|
||||
// ExtLinkLayerType returns the packet's hardware address type
|
||||
// (e.g. Ethernet, Infiniband).
|
||||
ExtLinkLayerType Extension = 28
|
||||
// ExtRXHash returns the packets receive hash.
|
||||
//
|
||||
// TODO: figure out what this rxhash actually is.
|
||||
ExtRXHash Extension = 32
|
||||
// ExtCPUID returns the ID of the CPU processing the current
|
||||
// packet.
|
||||
ExtCPUID Extension = 36
|
||||
// ExtVLANTag returns the packet's VLAN tag.
|
||||
ExtVLANTag Extension = 44
|
||||
// ExtVLANTagPresent returns non-zero if the packet has a VLAN
|
||||
// tag.
|
||||
//
|
||||
// TODO: I think this might be a lie: it reads bit 0x1000 of the
|
||||
// VLAN header, which changed meaning in recent revisions of the
|
||||
// spec - this extension may now return meaningless information.
|
||||
ExtVLANTagPresent Extension = 48
|
||||
// ExtVLANProto returns 0x8100 if the frame has a VLAN header,
|
||||
// 0x88a8 if the frame has a "Q-in-Q" double VLAN header, or some
|
||||
// other value if no VLAN information is present.
|
||||
ExtVLANProto Extension = 60
|
||||
// ExtRand returns a uniformly random uint32.
|
||||
ExtRand Extension = 56
|
||||
)
|
||||
|
||||
// The following gives names to various bit patterns used in opcode construction.
|
||||
|
||||
const (
|
||||
opMaskCls uint16 = 0x7
|
||||
// opClsLoad masks
|
||||
opMaskLoadDest = 0x01
|
||||
opMaskLoadWidth = 0x18
|
||||
opMaskLoadMode = 0xe0
|
||||
// opClsALU & opClsJump
|
||||
opMaskOperand = 0x08
|
||||
opMaskOperator = 0xf0
|
||||
)
|
||||
|
||||
const (
|
||||
// +---------------+-----------------+---+---+---+
|
||||
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 0 |
|
||||
// +---------------+-----------------+---+---+---+
|
||||
opClsLoadA uint16 = iota
|
||||
// +---------------+-----------------+---+---+---+
|
||||
// | AddrMode (3b) | LoadWidth (2b) | 0 | 0 | 1 |
|
||||
// +---------------+-----------------+---+---+---+
|
||||
opClsLoadX
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
opClsStoreA
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
// | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
|
||||
// +---+---+---+---+---+---+---+---+
|
||||
opClsStoreX
|
||||
// +---------------+-----------------+---+---+---+
|
||||
// | Operator (4b) | OperandSrc (1b) | 1 | 0 | 0 |
|
||||
// +---------------+-----------------+---+---+---+
|
||||
opClsALU
|
||||
// +-----------------------------+---+---+---+---+
|
||||
// | TestOperator (4b) | 0 | 1 | 0 | 1 |
|
||||
// +-----------------------------+---+---+---+---+
|
||||
opClsJump
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
// | 0 | 0 | 0 | RetSrc (1b) | 0 | 1 | 1 | 0 |
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
opClsReturn
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
// | 0 | 0 | 0 | TXAorTAX (1b) | 0 | 1 | 1 | 1 |
|
||||
// +---+-------------------------+---+---+---+---+
|
||||
opClsMisc
|
||||
)
|
||||
|
||||
const (
|
||||
opAddrModeImmediate uint16 = iota << 5
|
||||
opAddrModeAbsolute
|
||||
opAddrModeIndirect
|
||||
opAddrModeScratch
|
||||
opAddrModePacketLen // actually an extension, not an addressing mode.
|
||||
opAddrModeMemShift
|
||||
)
|
||||
|
||||
const (
|
||||
opLoadWidth4 uint16 = iota << 3
|
||||
opLoadWidth2
|
||||
opLoadWidth1
|
||||
)
|
||||
|
||||
// Operand for ALU and Jump instructions
|
||||
type opOperand uint16
|
||||
|
||||
// Supported operand sources.
|
||||
const (
|
||||
opOperandConstant opOperand = iota << 3
|
||||
opOperandX
|
||||
)
|
||||
|
||||
// An jumpOp is a conditional jump condition.
|
||||
type jumpOp uint16
|
||||
|
||||
// Supported jump conditions.
|
||||
const (
|
||||
opJumpAlways jumpOp = iota << 4
|
||||
opJumpEqual
|
||||
opJumpGT
|
||||
opJumpGE
|
||||
opJumpSet
|
||||
)
|
||||
|
||||
const (
|
||||
opRetSrcConstant uint16 = iota << 4
|
||||
opRetSrcA
|
||||
)
|
||||
|
||||
const (
|
||||
opMiscTAX = 0x00
|
||||
opMiscTXA = 0x80
|
||||
)
|
||||
82
vendor/golang.org/x/net/bpf/doc.go
generated
vendored
Normal file
82
vendor/golang.org/x/net/bpf/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
Package bpf implements marshaling and unmarshaling of programs for the
|
||||
Berkeley Packet Filter virtual machine, and provides a Go implementation
|
||||
of the virtual machine.
|
||||
|
||||
BPF's main use is to specify a packet filter for network taps, so that
|
||||
the kernel doesn't have to expensively copy every packet it sees to
|
||||
userspace. However, it's been repurposed to other areas where running
|
||||
user code in-kernel is needed. For example, Linux's seccomp uses BPF
|
||||
to apply security policies to system calls. For simplicity, this
|
||||
documentation refers only to packets, but other uses of BPF have their
|
||||
own data payloads.
|
||||
|
||||
BPF programs run in a restricted virtual machine. It has almost no
|
||||
access to kernel functions, and while conditional branches are
|
||||
allowed, they can only jump forwards, to guarantee that there are no
|
||||
infinite loops.
|
||||
|
||||
The virtual machine
|
||||
|
||||
The BPF VM is an accumulator machine. Its main register, called
|
||||
register A, is an implicit source and destination in all arithmetic
|
||||
and logic operations. The machine also has 16 scratch registers for
|
||||
temporary storage, and an indirection register (register X) for
|
||||
indirect memory access. All registers are 32 bits wide.
|
||||
|
||||
Each run of a BPF program is given one packet, which is placed in the
|
||||
VM's read-only "main memory". LoadAbsolute and LoadIndirect
|
||||
instructions can fetch up to 32 bits at a time into register A for
|
||||
examination.
|
||||
|
||||
The goal of a BPF program is to produce and return a verdict (uint32),
|
||||
which tells the kernel what to do with the packet. In the context of
|
||||
packet filtering, the returned value is the number of bytes of the
|
||||
packet to forward to userspace, or 0 to ignore the packet. Other
|
||||
contexts like seccomp define their own return values.
|
||||
|
||||
In order to simplify programs, attempts to read past the end of the
|
||||
packet terminate the program execution with a verdict of 0 (ignore
|
||||
packet). This means that the vast majority of BPF programs don't need
|
||||
to do any explicit bounds checking.
|
||||
|
||||
In addition to the bytes of the packet, some BPF programs have access
|
||||
to extensions, which are essentially calls to kernel utility
|
||||
functions. Currently, the only extensions supported by this package
|
||||
are the Linux packet filter extensions.
|
||||
|
||||
Examples
|
||||
|
||||
This packet filter selects all ARP packets.
|
||||
|
||||
bpf.Assemble([]bpf.Instruction{
|
||||
// Load "EtherType" field from the ethernet header.
|
||||
bpf.LoadAbsolute{Off: 12, Size: 2},
|
||||
// Skip over the next instruction if EtherType is not ARP.
|
||||
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x0806, SkipTrue: 1},
|
||||
// Verdict is "send up to 4k of the packet to userspace."
|
||||
bpf.RetConstant{Val: 4096},
|
||||
// Verdict is "ignore packet."
|
||||
bpf.RetConstant{Val: 0},
|
||||
})
|
||||
|
||||
This packet filter captures a random 1% sample of traffic.
|
||||
|
||||
bpf.Assemble([]bpf.Instruction{
|
||||
// Get a 32-bit random number from the Linux kernel.
|
||||
bpf.LoadExtension{Num: bpf.ExtRand},
|
||||
// 1% dice roll?
|
||||
bpf.JumpIf{Cond: bpf.JumpLessThan, Val: 2^32/100, SkipFalse: 1},
|
||||
// Capture.
|
||||
bpf.RetConstant{Val: 4096},
|
||||
// Ignore.
|
||||
bpf.RetConstant{Val: 0},
|
||||
})
|
||||
|
||||
*/
|
||||
package bpf // import "golang.org/x/net/bpf"
|
||||
726
vendor/golang.org/x/net/bpf/instructions.go
generated
vendored
Normal file
726
vendor/golang.org/x/net/bpf/instructions.go
generated
vendored
Normal file
|
|
@ -0,0 +1,726 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import "fmt"
|
||||
|
||||
// An Instruction is one instruction executed by the BPF virtual
|
||||
// machine.
|
||||
type Instruction interface {
|
||||
// Assemble assembles the Instruction into a RawInstruction.
|
||||
Assemble() (RawInstruction, error)
|
||||
}
|
||||
|
||||
// A RawInstruction is a raw BPF virtual machine instruction.
|
||||
type RawInstruction struct {
|
||||
// Operation to execute.
|
||||
Op uint16
|
||||
// For conditional jump instructions, the number of instructions
|
||||
// to skip if the condition is true/false.
|
||||
Jt uint8
|
||||
Jf uint8
|
||||
// Constant parameter. The meaning depends on the Op.
|
||||
K uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (ri RawInstruction) Assemble() (RawInstruction, error) { return ri, nil }
|
||||
|
||||
// Disassemble parses ri into an Instruction and returns it. If ri is
|
||||
// not recognized by this package, ri itself is returned.
|
||||
func (ri RawInstruction) Disassemble() Instruction {
|
||||
switch ri.Op & opMaskCls {
|
||||
case opClsLoadA, opClsLoadX:
|
||||
reg := Register(ri.Op & opMaskLoadDest)
|
||||
sz := 0
|
||||
switch ri.Op & opMaskLoadWidth {
|
||||
case opLoadWidth4:
|
||||
sz = 4
|
||||
case opLoadWidth2:
|
||||
sz = 2
|
||||
case opLoadWidth1:
|
||||
sz = 1
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
switch ri.Op & opMaskLoadMode {
|
||||
case opAddrModeImmediate:
|
||||
if sz != 4 {
|
||||
return ri
|
||||
}
|
||||
return LoadConstant{Dst: reg, Val: ri.K}
|
||||
case opAddrModeScratch:
|
||||
if sz != 4 || ri.K > 15 {
|
||||
return ri
|
||||
}
|
||||
return LoadScratch{Dst: reg, N: int(ri.K)}
|
||||
case opAddrModeAbsolute:
|
||||
if ri.K > extOffset+0xffffffff {
|
||||
return LoadExtension{Num: Extension(-extOffset + ri.K)}
|
||||
}
|
||||
return LoadAbsolute{Size: sz, Off: ri.K}
|
||||
case opAddrModeIndirect:
|
||||
return LoadIndirect{Size: sz, Off: ri.K}
|
||||
case opAddrModePacketLen:
|
||||
if sz != 4 {
|
||||
return ri
|
||||
}
|
||||
return LoadExtension{Num: ExtLen}
|
||||
case opAddrModeMemShift:
|
||||
return LoadMemShift{Off: ri.K}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsStoreA:
|
||||
if ri.Op != opClsStoreA || ri.K > 15 {
|
||||
return ri
|
||||
}
|
||||
return StoreScratch{Src: RegA, N: int(ri.K)}
|
||||
|
||||
case opClsStoreX:
|
||||
if ri.Op != opClsStoreX || ri.K > 15 {
|
||||
return ri
|
||||
}
|
||||
return StoreScratch{Src: RegX, N: int(ri.K)}
|
||||
|
||||
case opClsALU:
|
||||
switch op := ALUOp(ri.Op & opMaskOperator); op {
|
||||
case ALUOpAdd, ALUOpSub, ALUOpMul, ALUOpDiv, ALUOpOr, ALUOpAnd, ALUOpShiftLeft, ALUOpShiftRight, ALUOpMod, ALUOpXor:
|
||||
switch operand := opOperand(ri.Op & opMaskOperand); operand {
|
||||
case opOperandX:
|
||||
return ALUOpX{Op: op}
|
||||
case opOperandConstant:
|
||||
return ALUOpConstant{Op: op, Val: ri.K}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
case aluOpNeg:
|
||||
return NegateA{}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsJump:
|
||||
switch op := jumpOp(ri.Op & opMaskOperator); op {
|
||||
case opJumpAlways:
|
||||
return Jump{Skip: ri.K}
|
||||
case opJumpEqual, opJumpGT, opJumpGE, opJumpSet:
|
||||
cond, skipTrue, skipFalse := jumpOpToTest(op, ri.Jt, ri.Jf)
|
||||
switch operand := opOperand(ri.Op & opMaskOperand); operand {
|
||||
case opOperandX:
|
||||
return JumpIfX{Cond: cond, SkipTrue: skipTrue, SkipFalse: skipFalse}
|
||||
case opOperandConstant:
|
||||
return JumpIf{Cond: cond, Val: ri.K, SkipTrue: skipTrue, SkipFalse: skipFalse}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsReturn:
|
||||
switch ri.Op {
|
||||
case opClsReturn | opRetSrcA:
|
||||
return RetA{}
|
||||
case opClsReturn | opRetSrcConstant:
|
||||
return RetConstant{Val: ri.K}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
case opClsMisc:
|
||||
switch ri.Op {
|
||||
case opClsMisc | opMiscTAX:
|
||||
return TAX{}
|
||||
case opClsMisc | opMiscTXA:
|
||||
return TXA{}
|
||||
default:
|
||||
return ri
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable") // switch is exhaustive on the bit pattern
|
||||
}
|
||||
}
|
||||
|
||||
func jumpOpToTest(op jumpOp, skipTrue uint8, skipFalse uint8) (JumpTest, uint8, uint8) {
|
||||
var test JumpTest
|
||||
|
||||
// Decode "fake" jump conditions that don't appear in machine code
|
||||
// Ensures the Assemble -> Disassemble stage recreates the same instructions
|
||||
// See https://github.com/golang/go/issues/18470
|
||||
if skipTrue == 0 {
|
||||
switch op {
|
||||
case opJumpEqual:
|
||||
test = JumpNotEqual
|
||||
case opJumpGT:
|
||||
test = JumpLessOrEqual
|
||||
case opJumpGE:
|
||||
test = JumpLessThan
|
||||
case opJumpSet:
|
||||
test = JumpBitsNotSet
|
||||
}
|
||||
|
||||
return test, skipFalse, 0
|
||||
}
|
||||
|
||||
switch op {
|
||||
case opJumpEqual:
|
||||
test = JumpEqual
|
||||
case opJumpGT:
|
||||
test = JumpGreaterThan
|
||||
case opJumpGE:
|
||||
test = JumpGreaterOrEqual
|
||||
case opJumpSet:
|
||||
test = JumpBitsSet
|
||||
}
|
||||
|
||||
return test, skipTrue, skipFalse
|
||||
}
|
||||
|
||||
// LoadConstant loads Val into register Dst.
|
||||
type LoadConstant struct {
|
||||
Dst Register
|
||||
Val uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadConstant) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(a.Dst, 4, opAddrModeImmediate, a.Val)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadConstant) String() string {
|
||||
switch a.Dst {
|
||||
case RegA:
|
||||
return fmt.Sprintf("ld #%d", a.Val)
|
||||
case RegX:
|
||||
return fmt.Sprintf("ldx #%d", a.Val)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadScratch loads scratch[N] into register Dst.
|
||||
type LoadScratch struct {
|
||||
Dst Register
|
||||
N int // 0-15
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadScratch) Assemble() (RawInstruction, error) {
|
||||
if a.N < 0 || a.N > 15 {
|
||||
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||
}
|
||||
return assembleLoad(a.Dst, 4, opAddrModeScratch, uint32(a.N))
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadScratch) String() string {
|
||||
switch a.Dst {
|
||||
case RegA:
|
||||
return fmt.Sprintf("ld M[%d]", a.N)
|
||||
case RegX:
|
||||
return fmt.Sprintf("ldx M[%d]", a.N)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadAbsolute loads packet[Off:Off+Size] as an integer value into
|
||||
// register A.
|
||||
type LoadAbsolute struct {
|
||||
Off uint32
|
||||
Size int // 1, 2 or 4
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadAbsolute) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(RegA, a.Size, opAddrModeAbsolute, a.Off)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadAbsolute) String() string {
|
||||
switch a.Size {
|
||||
case 1: // byte
|
||||
return fmt.Sprintf("ldb [%d]", a.Off)
|
||||
case 2: // half word
|
||||
return fmt.Sprintf("ldh [%d]", a.Off)
|
||||
case 4: // word
|
||||
if a.Off > extOffset+0xffffffff {
|
||||
return LoadExtension{Num: Extension(a.Off + 0x1000)}.String()
|
||||
}
|
||||
return fmt.Sprintf("ld [%d]", a.Off)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadIndirect loads packet[X+Off:X+Off+Size] as an integer value
|
||||
// into register A.
|
||||
type LoadIndirect struct {
|
||||
Off uint32
|
||||
Size int // 1, 2 or 4
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadIndirect) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(RegA, a.Size, opAddrModeIndirect, a.Off)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadIndirect) String() string {
|
||||
switch a.Size {
|
||||
case 1: // byte
|
||||
return fmt.Sprintf("ldb [x + %d]", a.Off)
|
||||
case 2: // half word
|
||||
return fmt.Sprintf("ldh [x + %d]", a.Off)
|
||||
case 4: // word
|
||||
return fmt.Sprintf("ld [x + %d]", a.Off)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// LoadMemShift multiplies the first 4 bits of the byte at packet[Off]
|
||||
// by 4 and stores the result in register X.
|
||||
//
|
||||
// This instruction is mainly useful to load into X the length of an
|
||||
// IPv4 packet header in a single instruction, rather than have to do
|
||||
// the arithmetic on the header's first byte by hand.
|
||||
type LoadMemShift struct {
|
||||
Off uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadMemShift) Assemble() (RawInstruction, error) {
|
||||
return assembleLoad(RegX, 1, opAddrModeMemShift, a.Off)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadMemShift) String() string {
|
||||
return fmt.Sprintf("ldx 4*([%d]&0xf)", a.Off)
|
||||
}
|
||||
|
||||
// LoadExtension invokes a linux-specific extension and stores the
|
||||
// result in register A.
|
||||
type LoadExtension struct {
|
||||
Num Extension
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a LoadExtension) Assemble() (RawInstruction, error) {
|
||||
if a.Num == ExtLen {
|
||||
return assembleLoad(RegA, 4, opAddrModePacketLen, 0)
|
||||
}
|
||||
return assembleLoad(RegA, 4, opAddrModeAbsolute, uint32(extOffset+a.Num))
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a LoadExtension) String() string {
|
||||
switch a.Num {
|
||||
case ExtLen:
|
||||
return "ld #len"
|
||||
case ExtProto:
|
||||
return "ld #proto"
|
||||
case ExtType:
|
||||
return "ld #type"
|
||||
case ExtPayloadOffset:
|
||||
return "ld #poff"
|
||||
case ExtInterfaceIndex:
|
||||
return "ld #ifidx"
|
||||
case ExtNetlinkAttr:
|
||||
return "ld #nla"
|
||||
case ExtNetlinkAttrNested:
|
||||
return "ld #nlan"
|
||||
case ExtMark:
|
||||
return "ld #mark"
|
||||
case ExtQueue:
|
||||
return "ld #queue"
|
||||
case ExtLinkLayerType:
|
||||
return "ld #hatype"
|
||||
case ExtRXHash:
|
||||
return "ld #rxhash"
|
||||
case ExtCPUID:
|
||||
return "ld #cpu"
|
||||
case ExtVLANTag:
|
||||
return "ld #vlan_tci"
|
||||
case ExtVLANTagPresent:
|
||||
return "ld #vlan_avail"
|
||||
case ExtVLANProto:
|
||||
return "ld #vlan_tpid"
|
||||
case ExtRand:
|
||||
return "ld #rand"
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// StoreScratch stores register Src into scratch[N].
|
||||
type StoreScratch struct {
|
||||
Src Register
|
||||
N int // 0-15
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a StoreScratch) Assemble() (RawInstruction, error) {
|
||||
if a.N < 0 || a.N > 15 {
|
||||
return RawInstruction{}, fmt.Errorf("invalid scratch slot %d", a.N)
|
||||
}
|
||||
var op uint16
|
||||
switch a.Src {
|
||||
case RegA:
|
||||
op = opClsStoreA
|
||||
case RegX:
|
||||
op = opClsStoreX
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("invalid source register %v", a.Src)
|
||||
}
|
||||
|
||||
return RawInstruction{
|
||||
Op: op,
|
||||
K: uint32(a.N),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a StoreScratch) String() string {
|
||||
switch a.Src {
|
||||
case RegA:
|
||||
return fmt.Sprintf("st M[%d]", a.N)
|
||||
case RegX:
|
||||
return fmt.Sprintf("stx M[%d]", a.N)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// ALUOpConstant executes A = A <Op> Val.
|
||||
type ALUOpConstant struct {
|
||||
Op ALUOp
|
||||
Val uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a ALUOpConstant) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsALU | uint16(opOperandConstant) | uint16(a.Op),
|
||||
K: a.Val,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a ALUOpConstant) String() string {
|
||||
switch a.Op {
|
||||
case ALUOpAdd:
|
||||
return fmt.Sprintf("add #%d", a.Val)
|
||||
case ALUOpSub:
|
||||
return fmt.Sprintf("sub #%d", a.Val)
|
||||
case ALUOpMul:
|
||||
return fmt.Sprintf("mul #%d", a.Val)
|
||||
case ALUOpDiv:
|
||||
return fmt.Sprintf("div #%d", a.Val)
|
||||
case ALUOpMod:
|
||||
return fmt.Sprintf("mod #%d", a.Val)
|
||||
case ALUOpAnd:
|
||||
return fmt.Sprintf("and #%d", a.Val)
|
||||
case ALUOpOr:
|
||||
return fmt.Sprintf("or #%d", a.Val)
|
||||
case ALUOpXor:
|
||||
return fmt.Sprintf("xor #%d", a.Val)
|
||||
case ALUOpShiftLeft:
|
||||
return fmt.Sprintf("lsh #%d", a.Val)
|
||||
case ALUOpShiftRight:
|
||||
return fmt.Sprintf("rsh #%d", a.Val)
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// ALUOpX executes A = A <Op> X
|
||||
type ALUOpX struct {
|
||||
Op ALUOp
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a ALUOpX) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsALU | uint16(opOperandX) | uint16(a.Op),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a ALUOpX) String() string {
|
||||
switch a.Op {
|
||||
case ALUOpAdd:
|
||||
return "add x"
|
||||
case ALUOpSub:
|
||||
return "sub x"
|
||||
case ALUOpMul:
|
||||
return "mul x"
|
||||
case ALUOpDiv:
|
||||
return "div x"
|
||||
case ALUOpMod:
|
||||
return "mod x"
|
||||
case ALUOpAnd:
|
||||
return "and x"
|
||||
case ALUOpOr:
|
||||
return "or x"
|
||||
case ALUOpXor:
|
||||
return "xor x"
|
||||
case ALUOpShiftLeft:
|
||||
return "lsh x"
|
||||
case ALUOpShiftRight:
|
||||
return "rsh x"
|
||||
default:
|
||||
return fmt.Sprintf("unknown instruction: %#v", a)
|
||||
}
|
||||
}
|
||||
|
||||
// NegateA executes A = -A.
|
||||
type NegateA struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a NegateA) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsALU | uint16(aluOpNeg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a NegateA) String() string {
|
||||
return fmt.Sprintf("neg")
|
||||
}
|
||||
|
||||
// Jump skips the following Skip instructions in the program.
|
||||
type Jump struct {
|
||||
Skip uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a Jump) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsJump | uint16(opJumpAlways),
|
||||
K: a.Skip,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a Jump) String() string {
|
||||
return fmt.Sprintf("ja %d", a.Skip)
|
||||
}
|
||||
|
||||
// JumpIf skips the following Skip instructions in the program if A
|
||||
// <Cond> Val is true.
|
||||
type JumpIf struct {
|
||||
Cond JumpTest
|
||||
Val uint32
|
||||
SkipTrue uint8
|
||||
SkipFalse uint8
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a JumpIf) Assemble() (RawInstruction, error) {
|
||||
return jumpToRaw(a.Cond, opOperandConstant, a.Val, a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a JumpIf) String() string {
|
||||
return jumpToString(a.Cond, fmt.Sprintf("#%d", a.Val), a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// JumpIfX skips the following Skip instructions in the program if A
|
||||
// <Cond> X is true.
|
||||
type JumpIfX struct {
|
||||
Cond JumpTest
|
||||
SkipTrue uint8
|
||||
SkipFalse uint8
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a JumpIfX) Assemble() (RawInstruction, error) {
|
||||
return jumpToRaw(a.Cond, opOperandX, 0, a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a JumpIfX) String() string {
|
||||
return jumpToString(a.Cond, "x", a.SkipTrue, a.SkipFalse)
|
||||
}
|
||||
|
||||
// jumpToRaw assembles a jump instruction into a RawInstruction
|
||||
func jumpToRaw(test JumpTest, operand opOperand, k uint32, skipTrue, skipFalse uint8) (RawInstruction, error) {
|
||||
var (
|
||||
cond jumpOp
|
||||
flip bool
|
||||
)
|
||||
switch test {
|
||||
case JumpEqual:
|
||||
cond = opJumpEqual
|
||||
case JumpNotEqual:
|
||||
cond, flip = opJumpEqual, true
|
||||
case JumpGreaterThan:
|
||||
cond = opJumpGT
|
||||
case JumpLessThan:
|
||||
cond, flip = opJumpGE, true
|
||||
case JumpGreaterOrEqual:
|
||||
cond = opJumpGE
|
||||
case JumpLessOrEqual:
|
||||
cond, flip = opJumpGT, true
|
||||
case JumpBitsSet:
|
||||
cond = opJumpSet
|
||||
case JumpBitsNotSet:
|
||||
cond, flip = opJumpSet, true
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("unknown JumpTest %v", test)
|
||||
}
|
||||
jt, jf := skipTrue, skipFalse
|
||||
if flip {
|
||||
jt, jf = jf, jt
|
||||
}
|
||||
return RawInstruction{
|
||||
Op: opClsJump | uint16(cond) | uint16(operand),
|
||||
Jt: jt,
|
||||
Jf: jf,
|
||||
K: k,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// jumpToString converts a jump instruction to assembler notation
|
||||
func jumpToString(cond JumpTest, operand string, skipTrue, skipFalse uint8) string {
|
||||
switch cond {
|
||||
// K == A
|
||||
case JumpEqual:
|
||||
return conditionalJump(operand, skipTrue, skipFalse, "jeq", "jneq")
|
||||
// K != A
|
||||
case JumpNotEqual:
|
||||
return fmt.Sprintf("jneq %s,%d", operand, skipTrue)
|
||||
// K > A
|
||||
case JumpGreaterThan:
|
||||
return conditionalJump(operand, skipTrue, skipFalse, "jgt", "jle")
|
||||
// K < A
|
||||
case JumpLessThan:
|
||||
return fmt.Sprintf("jlt %s,%d", operand, skipTrue)
|
||||
// K >= A
|
||||
case JumpGreaterOrEqual:
|
||||
return conditionalJump(operand, skipTrue, skipFalse, "jge", "jlt")
|
||||
// K <= A
|
||||
case JumpLessOrEqual:
|
||||
return fmt.Sprintf("jle %s,%d", operand, skipTrue)
|
||||
// K & A != 0
|
||||
case JumpBitsSet:
|
||||
if skipFalse > 0 {
|
||||
return fmt.Sprintf("jset %s,%d,%d", operand, skipTrue, skipFalse)
|
||||
}
|
||||
return fmt.Sprintf("jset %s,%d", operand, skipTrue)
|
||||
// K & A == 0, there is no assembler instruction for JumpBitNotSet, use JumpBitSet and invert skips
|
||||
case JumpBitsNotSet:
|
||||
return jumpToString(JumpBitsSet, operand, skipFalse, skipTrue)
|
||||
default:
|
||||
return fmt.Sprintf("unknown JumpTest %#v", cond)
|
||||
}
|
||||
}
|
||||
|
||||
func conditionalJump(operand string, skipTrue, skipFalse uint8, positiveJump, negativeJump string) string {
|
||||
if skipTrue > 0 {
|
||||
if skipFalse > 0 {
|
||||
return fmt.Sprintf("%s %s,%d,%d", positiveJump, operand, skipTrue, skipFalse)
|
||||
}
|
||||
return fmt.Sprintf("%s %s,%d", positiveJump, operand, skipTrue)
|
||||
}
|
||||
return fmt.Sprintf("%s %s,%d", negativeJump, operand, skipFalse)
|
||||
}
|
||||
|
||||
// RetA exits the BPF program, returning the value of register A.
|
||||
type RetA struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a RetA) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsReturn | opRetSrcA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a RetA) String() string {
|
||||
return fmt.Sprintf("ret a")
|
||||
}
|
||||
|
||||
// RetConstant exits the BPF program, returning a constant value.
|
||||
type RetConstant struct {
|
||||
Val uint32
|
||||
}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a RetConstant) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsReturn | opRetSrcConstant,
|
||||
K: a.Val,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a RetConstant) String() string {
|
||||
return fmt.Sprintf("ret #%d", a.Val)
|
||||
}
|
||||
|
||||
// TXA copies the value of register X to register A.
|
||||
type TXA struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a TXA) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsMisc | opMiscTXA,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a TXA) String() string {
|
||||
return fmt.Sprintf("txa")
|
||||
}
|
||||
|
||||
// TAX copies the value of register A to register X.
|
||||
type TAX struct{}
|
||||
|
||||
// Assemble implements the Instruction Assemble method.
|
||||
func (a TAX) Assemble() (RawInstruction, error) {
|
||||
return RawInstruction{
|
||||
Op: opClsMisc | opMiscTAX,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// String returns the instruction in assembler notation.
|
||||
func (a TAX) String() string {
|
||||
return fmt.Sprintf("tax")
|
||||
}
|
||||
|
||||
func assembleLoad(dst Register, loadSize int, mode uint16, k uint32) (RawInstruction, error) {
|
||||
var (
|
||||
cls uint16
|
||||
sz uint16
|
||||
)
|
||||
switch dst {
|
||||
case RegA:
|
||||
cls = opClsLoadA
|
||||
case RegX:
|
||||
cls = opClsLoadX
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("invalid target register %v", dst)
|
||||
}
|
||||
switch loadSize {
|
||||
case 1:
|
||||
sz = opLoadWidth1
|
||||
case 2:
|
||||
sz = opLoadWidth2
|
||||
case 4:
|
||||
sz = opLoadWidth4
|
||||
default:
|
||||
return RawInstruction{}, fmt.Errorf("invalid load byte length %d", sz)
|
||||
}
|
||||
return RawInstruction{
|
||||
Op: cls | sz | mode,
|
||||
K: k,
|
||||
}, nil
|
||||
}
|
||||
10
vendor/golang.org/x/net/bpf/setter.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/bpf/setter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
// A Setter is a type which can attach a compiled BPF filter to itself.
|
||||
type Setter interface {
|
||||
SetBPF(filter []RawInstruction) error
|
||||
}
|
||||
150
vendor/golang.org/x/net/bpf/vm.go
generated
vendored
Normal file
150
vendor/golang.org/x/net/bpf/vm.go
generated
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A VM is an emulated BPF virtual machine.
|
||||
type VM struct {
|
||||
filter []Instruction
|
||||
}
|
||||
|
||||
// NewVM returns a new VM using the input BPF program.
|
||||
func NewVM(filter []Instruction) (*VM, error) {
|
||||
if len(filter) == 0 {
|
||||
return nil, errors.New("one or more Instructions must be specified")
|
||||
}
|
||||
|
||||
for i, ins := range filter {
|
||||
check := len(filter) - (i + 1)
|
||||
switch ins := ins.(type) {
|
||||
// Check for out-of-bounds jumps in instructions
|
||||
case Jump:
|
||||
if check <= int(ins.Skip) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions; jumping past program bounds", ins.Skip)
|
||||
}
|
||||
case JumpIf:
|
||||
if check <= int(ins.SkipTrue) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||
}
|
||||
if check <= int(ins.SkipFalse) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||
}
|
||||
case JumpIfX:
|
||||
if check <= int(ins.SkipTrue) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in true case; jumping past program bounds", ins.SkipTrue)
|
||||
}
|
||||
if check <= int(ins.SkipFalse) {
|
||||
return nil, fmt.Errorf("cannot jump %d instructions in false case; jumping past program bounds", ins.SkipFalse)
|
||||
}
|
||||
// Check for division or modulus by zero
|
||||
case ALUOpConstant:
|
||||
if ins.Val != 0 {
|
||||
break
|
||||
}
|
||||
|
||||
switch ins.Op {
|
||||
case ALUOpDiv, ALUOpMod:
|
||||
return nil, errors.New("cannot divide by zero using ALUOpConstant")
|
||||
}
|
||||
// Check for unknown extensions
|
||||
case LoadExtension:
|
||||
switch ins.Num {
|
||||
case ExtLen:
|
||||
default:
|
||||
return nil, fmt.Errorf("extension %d not implemented", ins.Num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure last instruction is a return instruction
|
||||
switch filter[len(filter)-1].(type) {
|
||||
case RetA, RetConstant:
|
||||
default:
|
||||
return nil, errors.New("BPF program must end with RetA or RetConstant")
|
||||
}
|
||||
|
||||
// Though our VM works using disassembled instructions, we
|
||||
// attempt to assemble the input filter anyway to ensure it is compatible
|
||||
// with an operating system VM.
|
||||
_, err := Assemble(filter)
|
||||
|
||||
return &VM{
|
||||
filter: filter,
|
||||
}, err
|
||||
}
|
||||
|
||||
// Run runs the VM's BPF program against the input bytes.
|
||||
// Run returns the number of bytes accepted by the BPF program, and any errors
|
||||
// which occurred while processing the program.
|
||||
func (v *VM) Run(in []byte) (int, error) {
|
||||
var (
|
||||
// Registers of the virtual machine
|
||||
regA uint32
|
||||
regX uint32
|
||||
regScratch [16]uint32
|
||||
|
||||
// OK is true if the program should continue processing the next
|
||||
// instruction, or false if not, causing the loop to break
|
||||
ok = true
|
||||
)
|
||||
|
||||
// TODO(mdlayher): implement:
|
||||
// - NegateA:
|
||||
// - would require a change from uint32 registers to int32
|
||||
// registers
|
||||
|
||||
// TODO(mdlayher): add interop tests that check signedness of ALU
|
||||
// operations against kernel implementation, and make sure Go
|
||||
// implementation matches behavior
|
||||
|
||||
for i := 0; i < len(v.filter) && ok; i++ {
|
||||
ins := v.filter[i]
|
||||
|
||||
switch ins := ins.(type) {
|
||||
case ALUOpConstant:
|
||||
regA = aluOpConstant(ins, regA)
|
||||
case ALUOpX:
|
||||
regA, ok = aluOpX(ins, regA, regX)
|
||||
case Jump:
|
||||
i += int(ins.Skip)
|
||||
case JumpIf:
|
||||
jump := jumpIf(ins, regA)
|
||||
i += jump
|
||||
case JumpIfX:
|
||||
jump := jumpIfX(ins, regA, regX)
|
||||
i += jump
|
||||
case LoadAbsolute:
|
||||
regA, ok = loadAbsolute(ins, in)
|
||||
case LoadConstant:
|
||||
regA, regX = loadConstant(ins, regA, regX)
|
||||
case LoadExtension:
|
||||
regA = loadExtension(ins, in)
|
||||
case LoadIndirect:
|
||||
regA, ok = loadIndirect(ins, in, regX)
|
||||
case LoadMemShift:
|
||||
regX, ok = loadMemShift(ins, in)
|
||||
case LoadScratch:
|
||||
regA, regX = loadScratch(ins, regScratch, regA, regX)
|
||||
case RetA:
|
||||
return int(regA), nil
|
||||
case RetConstant:
|
||||
return int(ins.Val), nil
|
||||
case StoreScratch:
|
||||
regScratch = storeScratch(ins, regScratch, regA, regX)
|
||||
case TAX:
|
||||
regX = regA
|
||||
case TXA:
|
||||
regA = regX
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown Instruction at index %d: %T", i, ins)
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
182
vendor/golang.org/x/net/bpf/vm_instructions.go
generated
vendored
Normal file
182
vendor/golang.org/x/net/bpf/vm_instructions.go
generated
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package bpf
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func aluOpConstant(ins ALUOpConstant, regA uint32) uint32 {
|
||||
return aluOpCommon(ins.Op, regA, ins.Val)
|
||||
}
|
||||
|
||||
func aluOpX(ins ALUOpX, regA uint32, regX uint32) (uint32, bool) {
|
||||
// Guard against division or modulus by zero by terminating
|
||||
// the program, as the OS BPF VM does
|
||||
if regX == 0 {
|
||||
switch ins.Op {
|
||||
case ALUOpDiv, ALUOpMod:
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
return aluOpCommon(ins.Op, regA, regX), true
|
||||
}
|
||||
|
||||
func aluOpCommon(op ALUOp, regA uint32, value uint32) uint32 {
|
||||
switch op {
|
||||
case ALUOpAdd:
|
||||
return regA + value
|
||||
case ALUOpSub:
|
||||
return regA - value
|
||||
case ALUOpMul:
|
||||
return regA * value
|
||||
case ALUOpDiv:
|
||||
// Division by zero not permitted by NewVM and aluOpX checks
|
||||
return regA / value
|
||||
case ALUOpOr:
|
||||
return regA | value
|
||||
case ALUOpAnd:
|
||||
return regA & value
|
||||
case ALUOpShiftLeft:
|
||||
return regA << value
|
||||
case ALUOpShiftRight:
|
||||
return regA >> value
|
||||
case ALUOpMod:
|
||||
// Modulus by zero not permitted by NewVM and aluOpX checks
|
||||
return regA % value
|
||||
case ALUOpXor:
|
||||
return regA ^ value
|
||||
default:
|
||||
return regA
|
||||
}
|
||||
}
|
||||
|
||||
func jumpIf(ins JumpIf, regA uint32) int {
|
||||
return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, ins.Val)
|
||||
}
|
||||
|
||||
func jumpIfX(ins JumpIfX, regA uint32, regX uint32) int {
|
||||
return jumpIfCommon(ins.Cond, ins.SkipTrue, ins.SkipFalse, regA, regX)
|
||||
}
|
||||
|
||||
func jumpIfCommon(cond JumpTest, skipTrue, skipFalse uint8, regA uint32, value uint32) int {
|
||||
var ok bool
|
||||
|
||||
switch cond {
|
||||
case JumpEqual:
|
||||
ok = regA == value
|
||||
case JumpNotEqual:
|
||||
ok = regA != value
|
||||
case JumpGreaterThan:
|
||||
ok = regA > value
|
||||
case JumpLessThan:
|
||||
ok = regA < value
|
||||
case JumpGreaterOrEqual:
|
||||
ok = regA >= value
|
||||
case JumpLessOrEqual:
|
||||
ok = regA <= value
|
||||
case JumpBitsSet:
|
||||
ok = (regA & value) != 0
|
||||
case JumpBitsNotSet:
|
||||
ok = (regA & value) == 0
|
||||
}
|
||||
|
||||
if ok {
|
||||
return int(skipTrue)
|
||||
}
|
||||
|
||||
return int(skipFalse)
|
||||
}
|
||||
|
||||
func loadAbsolute(ins LoadAbsolute, in []byte) (uint32, bool) {
|
||||
offset := int(ins.Off)
|
||||
size := int(ins.Size)
|
||||
|
||||
return loadCommon(in, offset, size)
|
||||
}
|
||||
|
||||
func loadConstant(ins LoadConstant, regA uint32, regX uint32) (uint32, uint32) {
|
||||
switch ins.Dst {
|
||||
case RegA:
|
||||
regA = ins.Val
|
||||
case RegX:
|
||||
regX = ins.Val
|
||||
}
|
||||
|
||||
return regA, regX
|
||||
}
|
||||
|
||||
func loadExtension(ins LoadExtension, in []byte) uint32 {
|
||||
switch ins.Num {
|
||||
case ExtLen:
|
||||
return uint32(len(in))
|
||||
default:
|
||||
panic(fmt.Sprintf("unimplemented extension: %d", ins.Num))
|
||||
}
|
||||
}
|
||||
|
||||
func loadIndirect(ins LoadIndirect, in []byte, regX uint32) (uint32, bool) {
|
||||
offset := int(ins.Off) + int(regX)
|
||||
size := int(ins.Size)
|
||||
|
||||
return loadCommon(in, offset, size)
|
||||
}
|
||||
|
||||
func loadMemShift(ins LoadMemShift, in []byte) (uint32, bool) {
|
||||
offset := int(ins.Off)
|
||||
|
||||
// Size of LoadMemShift is always 1 byte
|
||||
if !inBounds(len(in), offset, 1) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Mask off high 4 bits and multiply low 4 bits by 4
|
||||
return uint32(in[offset]&0x0f) * 4, true
|
||||
}
|
||||
|
||||
func inBounds(inLen int, offset int, size int) bool {
|
||||
return offset+size <= inLen
|
||||
}
|
||||
|
||||
func loadCommon(in []byte, offset int, size int) (uint32, bool) {
|
||||
if !inBounds(len(in), offset, size) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
switch size {
|
||||
case 1:
|
||||
return uint32(in[offset]), true
|
||||
case 2:
|
||||
return uint32(binary.BigEndian.Uint16(in[offset : offset+size])), true
|
||||
case 4:
|
||||
return uint32(binary.BigEndian.Uint32(in[offset : offset+size])), true
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid load size: %d", size))
|
||||
}
|
||||
}
|
||||
|
||||
func loadScratch(ins LoadScratch, regScratch [16]uint32, regA uint32, regX uint32) (uint32, uint32) {
|
||||
switch ins.Dst {
|
||||
case RegA:
|
||||
regA = regScratch[ins.N]
|
||||
case RegX:
|
||||
regX = regScratch[ins.N]
|
||||
}
|
||||
|
||||
return regA, regX
|
||||
}
|
||||
|
||||
func storeScratch(ins StoreScratch, regScratch [16]uint32, regA uint32, regX uint32) [16]uint32 {
|
||||
switch ins.Src {
|
||||
case RegA:
|
||||
regScratch[ins.N] = regA
|
||||
case RegX:
|
||||
regScratch[ins.N] = regX
|
||||
}
|
||||
|
||||
return regScratch
|
||||
}
|
||||
223
vendor/golang.org/x/net/internal/iana/const.go
generated
vendored
Normal file
223
vendor/golang.org/x/net/internal/iana/const.go
generated
vendored
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
// go generate gen.go
|
||||
// Code generated by the command above; DO NOT EDIT.
|
||||
|
||||
// Package iana provides protocol number resources managed by the Internet Assigned Numbers Authority (IANA).
|
||||
package iana // import "golang.org/x/net/internal/iana"
|
||||
|
||||
// Differentiated Services Field Codepoints (DSCP), Updated: 2018-05-04
|
||||
const (
|
||||
DiffServCS0 = 0x00 // CS0
|
||||
DiffServCS1 = 0x20 // CS1
|
||||
DiffServCS2 = 0x40 // CS2
|
||||
DiffServCS3 = 0x60 // CS3
|
||||
DiffServCS4 = 0x80 // CS4
|
||||
DiffServCS5 = 0xa0 // CS5
|
||||
DiffServCS6 = 0xc0 // CS6
|
||||
DiffServCS7 = 0xe0 // CS7
|
||||
DiffServAF11 = 0x28 // AF11
|
||||
DiffServAF12 = 0x30 // AF12
|
||||
DiffServAF13 = 0x38 // AF13
|
||||
DiffServAF21 = 0x48 // AF21
|
||||
DiffServAF22 = 0x50 // AF22
|
||||
DiffServAF23 = 0x58 // AF23
|
||||
DiffServAF31 = 0x68 // AF31
|
||||
DiffServAF32 = 0x70 // AF32
|
||||
DiffServAF33 = 0x78 // AF33
|
||||
DiffServAF41 = 0x88 // AF41
|
||||
DiffServAF42 = 0x90 // AF42
|
||||
DiffServAF43 = 0x98 // AF43
|
||||
DiffServEF = 0xb8 // EF
|
||||
DiffServVOICEADMIT = 0xb0 // VOICE-ADMIT
|
||||
NotECNTransport = 0x00 // Not-ECT (Not ECN-Capable Transport)
|
||||
ECNTransport1 = 0x01 // ECT(1) (ECN-Capable Transport(1))
|
||||
ECNTransport0 = 0x02 // ECT(0) (ECN-Capable Transport(0))
|
||||
CongestionExperienced = 0x03 // CE (Congestion Experienced)
|
||||
)
|
||||
|
||||
// Protocol Numbers, Updated: 2017-10-13
|
||||
const (
|
||||
ProtocolIP = 0 // IPv4 encapsulation, pseudo protocol number
|
||||
ProtocolHOPOPT = 0 // IPv6 Hop-by-Hop Option
|
||||
ProtocolICMP = 1 // Internet Control Message
|
||||
ProtocolIGMP = 2 // Internet Group Management
|
||||
ProtocolGGP = 3 // Gateway-to-Gateway
|
||||
ProtocolIPv4 = 4 // IPv4 encapsulation
|
||||
ProtocolST = 5 // Stream
|
||||
ProtocolTCP = 6 // Transmission Control
|
||||
ProtocolCBT = 7 // CBT
|
||||
ProtocolEGP = 8 // Exterior Gateway Protocol
|
||||
ProtocolIGP = 9 // any private interior gateway (used by Cisco for their IGRP)
|
||||
ProtocolBBNRCCMON = 10 // BBN RCC Monitoring
|
||||
ProtocolNVPII = 11 // Network Voice Protocol
|
||||
ProtocolPUP = 12 // PUP
|
||||
ProtocolEMCON = 14 // EMCON
|
||||
ProtocolXNET = 15 // Cross Net Debugger
|
||||
ProtocolCHAOS = 16 // Chaos
|
||||
ProtocolUDP = 17 // User Datagram
|
||||
ProtocolMUX = 18 // Multiplexing
|
||||
ProtocolDCNMEAS = 19 // DCN Measurement Subsystems
|
||||
ProtocolHMP = 20 // Host Monitoring
|
||||
ProtocolPRM = 21 // Packet Radio Measurement
|
||||
ProtocolXNSIDP = 22 // XEROX NS IDP
|
||||
ProtocolTRUNK1 = 23 // Trunk-1
|
||||
ProtocolTRUNK2 = 24 // Trunk-2
|
||||
ProtocolLEAF1 = 25 // Leaf-1
|
||||
ProtocolLEAF2 = 26 // Leaf-2
|
||||
ProtocolRDP = 27 // Reliable Data Protocol
|
||||
ProtocolIRTP = 28 // Internet Reliable Transaction
|
||||
ProtocolISOTP4 = 29 // ISO Transport Protocol Class 4
|
||||
ProtocolNETBLT = 30 // Bulk Data Transfer Protocol
|
||||
ProtocolMFENSP = 31 // MFE Network Services Protocol
|
||||
ProtocolMERITINP = 32 // MERIT Internodal Protocol
|
||||
ProtocolDCCP = 33 // Datagram Congestion Control Protocol
|
||||
Protocol3PC = 34 // Third Party Connect Protocol
|
||||
ProtocolIDPR = 35 // Inter-Domain Policy Routing Protocol
|
||||
ProtocolXTP = 36 // XTP
|
||||
ProtocolDDP = 37 // Datagram Delivery Protocol
|
||||
ProtocolIDPRCMTP = 38 // IDPR Control Message Transport Proto
|
||||
ProtocolTPPP = 39 // TP++ Transport Protocol
|
||||
ProtocolIL = 40 // IL Transport Protocol
|
||||
ProtocolIPv6 = 41 // IPv6 encapsulation
|
||||
ProtocolSDRP = 42 // Source Demand Routing Protocol
|
||||
ProtocolIPv6Route = 43 // Routing Header for IPv6
|
||||
ProtocolIPv6Frag = 44 // Fragment Header for IPv6
|
||||
ProtocolIDRP = 45 // Inter-Domain Routing Protocol
|
||||
ProtocolRSVP = 46 // Reservation Protocol
|
||||
ProtocolGRE = 47 // Generic Routing Encapsulation
|
||||
ProtocolDSR = 48 // Dynamic Source Routing Protocol
|
||||
ProtocolBNA = 49 // BNA
|
||||
ProtocolESP = 50 // Encap Security Payload
|
||||
ProtocolAH = 51 // Authentication Header
|
||||
ProtocolINLSP = 52 // Integrated Net Layer Security TUBA
|
||||
ProtocolNARP = 54 // NBMA Address Resolution Protocol
|
||||
ProtocolMOBILE = 55 // IP Mobility
|
||||
ProtocolTLSP = 56 // Transport Layer Security Protocol using Kryptonet key management
|
||||
ProtocolSKIP = 57 // SKIP
|
||||
ProtocolIPv6ICMP = 58 // ICMP for IPv6
|
||||
ProtocolIPv6NoNxt = 59 // No Next Header for IPv6
|
||||
ProtocolIPv6Opts = 60 // Destination Options for IPv6
|
||||
ProtocolCFTP = 62 // CFTP
|
||||
ProtocolSATEXPAK = 64 // SATNET and Backroom EXPAK
|
||||
ProtocolKRYPTOLAN = 65 // Kryptolan
|
||||
ProtocolRVD = 66 // MIT Remote Virtual Disk Protocol
|
||||
ProtocolIPPC = 67 // Internet Pluribus Packet Core
|
||||
ProtocolSATMON = 69 // SATNET Monitoring
|
||||
ProtocolVISA = 70 // VISA Protocol
|
||||
ProtocolIPCV = 71 // Internet Packet Core Utility
|
||||
ProtocolCPNX = 72 // Computer Protocol Network Executive
|
||||
ProtocolCPHB = 73 // Computer Protocol Heart Beat
|
||||
ProtocolWSN = 74 // Wang Span Network
|
||||
ProtocolPVP = 75 // Packet Video Protocol
|
||||
ProtocolBRSATMON = 76 // Backroom SATNET Monitoring
|
||||
ProtocolSUNND = 77 // SUN ND PROTOCOL-Temporary
|
||||
ProtocolWBMON = 78 // WIDEBAND Monitoring
|
||||
ProtocolWBEXPAK = 79 // WIDEBAND EXPAK
|
||||
ProtocolISOIP = 80 // ISO Internet Protocol
|
||||
ProtocolVMTP = 81 // VMTP
|
||||
ProtocolSECUREVMTP = 82 // SECURE-VMTP
|
||||
ProtocolVINES = 83 // VINES
|
||||
ProtocolTTP = 84 // Transaction Transport Protocol
|
||||
ProtocolIPTM = 84 // Internet Protocol Traffic Manager
|
||||
ProtocolNSFNETIGP = 85 // NSFNET-IGP
|
||||
ProtocolDGP = 86 // Dissimilar Gateway Protocol
|
||||
ProtocolTCF = 87 // TCF
|
||||
ProtocolEIGRP = 88 // EIGRP
|
||||
ProtocolOSPFIGP = 89 // OSPFIGP
|
||||
ProtocolSpriteRPC = 90 // Sprite RPC Protocol
|
||||
ProtocolLARP = 91 // Locus Address Resolution Protocol
|
||||
ProtocolMTP = 92 // Multicast Transport Protocol
|
||||
ProtocolAX25 = 93 // AX.25 Frames
|
||||
ProtocolIPIP = 94 // IP-within-IP Encapsulation Protocol
|
||||
ProtocolSCCSP = 96 // Semaphore Communications Sec. Pro.
|
||||
ProtocolETHERIP = 97 // Ethernet-within-IP Encapsulation
|
||||
ProtocolENCAP = 98 // Encapsulation Header
|
||||
ProtocolGMTP = 100 // GMTP
|
||||
ProtocolIFMP = 101 // Ipsilon Flow Management Protocol
|
||||
ProtocolPNNI = 102 // PNNI over IP
|
||||
ProtocolPIM = 103 // Protocol Independent Multicast
|
||||
ProtocolARIS = 104 // ARIS
|
||||
ProtocolSCPS = 105 // SCPS
|
||||
ProtocolQNX = 106 // QNX
|
||||
ProtocolAN = 107 // Active Networks
|
||||
ProtocolIPComp = 108 // IP Payload Compression Protocol
|
||||
ProtocolSNP = 109 // Sitara Networks Protocol
|
||||
ProtocolCompaqPeer = 110 // Compaq Peer Protocol
|
||||
ProtocolIPXinIP = 111 // IPX in IP
|
||||
ProtocolVRRP = 112 // Virtual Router Redundancy Protocol
|
||||
ProtocolPGM = 113 // PGM Reliable Transport Protocol
|
||||
ProtocolL2TP = 115 // Layer Two Tunneling Protocol
|
||||
ProtocolDDX = 116 // D-II Data Exchange (DDX)
|
||||
ProtocolIATP = 117 // Interactive Agent Transfer Protocol
|
||||
ProtocolSTP = 118 // Schedule Transfer Protocol
|
||||
ProtocolSRP = 119 // SpectraLink Radio Protocol
|
||||
ProtocolUTI = 120 // UTI
|
||||
ProtocolSMP = 121 // Simple Message Protocol
|
||||
ProtocolPTP = 123 // Performance Transparency Protocol
|
||||
ProtocolISIS = 124 // ISIS over IPv4
|
||||
ProtocolFIRE = 125 // FIRE
|
||||
ProtocolCRTP = 126 // Combat Radio Transport Protocol
|
||||
ProtocolCRUDP = 127 // Combat Radio User Datagram
|
||||
ProtocolSSCOPMCE = 128 // SSCOPMCE
|
||||
ProtocolIPLT = 129 // IPLT
|
||||
ProtocolSPS = 130 // Secure Packet Shield
|
||||
ProtocolPIPE = 131 // Private IP Encapsulation within IP
|
||||
ProtocolSCTP = 132 // Stream Control Transmission Protocol
|
||||
ProtocolFC = 133 // Fibre Channel
|
||||
ProtocolRSVPE2EIGNORE = 134 // RSVP-E2E-IGNORE
|
||||
ProtocolMobilityHeader = 135 // Mobility Header
|
||||
ProtocolUDPLite = 136 // UDPLite
|
||||
ProtocolMPLSinIP = 137 // MPLS-in-IP
|
||||
ProtocolMANET = 138 // MANET Protocols
|
||||
ProtocolHIP = 139 // Host Identity Protocol
|
||||
ProtocolShim6 = 140 // Shim6 Protocol
|
||||
ProtocolWESP = 141 // Wrapped Encapsulating Security Payload
|
||||
ProtocolROHC = 142 // Robust Header Compression
|
||||
ProtocolReserved = 255 // Reserved
|
||||
)
|
||||
|
||||
// Address Family Numbers, Updated: 2018-04-02
|
||||
const (
|
||||
AddrFamilyIPv4 = 1 // IP (IP version 4)
|
||||
AddrFamilyIPv6 = 2 // IP6 (IP version 6)
|
||||
AddrFamilyNSAP = 3 // NSAP
|
||||
AddrFamilyHDLC = 4 // HDLC (8-bit multidrop)
|
||||
AddrFamilyBBN1822 = 5 // BBN 1822
|
||||
AddrFamily802 = 6 // 802 (includes all 802 media plus Ethernet "canonical format")
|
||||
AddrFamilyE163 = 7 // E.163
|
||||
AddrFamilyE164 = 8 // E.164 (SMDS, Frame Relay, ATM)
|
||||
AddrFamilyF69 = 9 // F.69 (Telex)
|
||||
AddrFamilyX121 = 10 // X.121 (X.25, Frame Relay)
|
||||
AddrFamilyIPX = 11 // IPX
|
||||
AddrFamilyAppletalk = 12 // Appletalk
|
||||
AddrFamilyDecnetIV = 13 // Decnet IV
|
||||
AddrFamilyBanyanVines = 14 // Banyan Vines
|
||||
AddrFamilyE164withSubaddress = 15 // E.164 with NSAP format subaddress
|
||||
AddrFamilyDNS = 16 // DNS (Domain Name System)
|
||||
AddrFamilyDistinguishedName = 17 // Distinguished Name
|
||||
AddrFamilyASNumber = 18 // AS Number
|
||||
AddrFamilyXTPoverIPv4 = 19 // XTP over IP version 4
|
||||
AddrFamilyXTPoverIPv6 = 20 // XTP over IP version 6
|
||||
AddrFamilyXTPnativemodeXTP = 21 // XTP native mode XTP
|
||||
AddrFamilyFibreChannelWorldWidePortName = 22 // Fibre Channel World-Wide Port Name
|
||||
AddrFamilyFibreChannelWorldWideNodeName = 23 // Fibre Channel World-Wide Node Name
|
||||
AddrFamilyGWID = 24 // GWID
|
||||
AddrFamilyL2VPN = 25 // AFI for L2VPN information
|
||||
AddrFamilyMPLSTPSectionEndpointID = 26 // MPLS-TP Section Endpoint Identifier
|
||||
AddrFamilyMPLSTPLSPEndpointID = 27 // MPLS-TP LSP Endpoint Identifier
|
||||
AddrFamilyMPLSTPPseudowireEndpointID = 28 // MPLS-TP Pseudowire Endpoint Identifier
|
||||
AddrFamilyMTIPv4 = 29 // MT IP: Multi-Topology IP version 4
|
||||
AddrFamilyMTIPv6 = 30 // MT IPv6: Multi-Topology IP version 6
|
||||
AddrFamilyEIGRPCommonServiceFamily = 16384 // EIGRP Common Service Family
|
||||
AddrFamilyEIGRPIPv4ServiceFamily = 16385 // EIGRP IPv4 Service Family
|
||||
AddrFamilyEIGRPIPv6ServiceFamily = 16386 // EIGRP IPv6 Service Family
|
||||
AddrFamilyLISPCanonicalAddressFormat = 16387 // LISP Canonical Address Format (LCAF)
|
||||
AddrFamilyBGPLS = 16388 // BGP-LS
|
||||
AddrFamily48bitMAC = 16389 // 48-bit MAC
|
||||
AddrFamily64bitMAC = 16390 // 64-bit MAC
|
||||
AddrFamilyOUI = 16391 // OUI
|
||||
AddrFamilyMACFinal24bits = 16392 // MAC/24
|
||||
AddrFamilyMACFinal40bits = 16393 // MAC/40
|
||||
AddrFamilyIPv6Initial64bits = 16394 // IPv6/64
|
||||
AddrFamilyRBridgePortID = 16395 // RBridge Port ID
|
||||
AddrFamilyTRILLNickname = 16396 // TRILL Nickname
|
||||
)
|
||||
11
vendor/golang.org/x/net/internal/socket/cmsghdr.go
generated
vendored
Normal file
11
vendor/golang.org/x/net/internal/socket/cmsghdr.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) len() int { return int(h.Len) }
|
||||
func (h *cmsghdr) lvl() int { return int(h.Level) }
|
||||
func (h *cmsghdr) typ() int { return int(h.Type) }
|
||||
13
vendor/golang.org/x/net/internal/socket/cmsghdr_bsd.go
generated
vendored
Normal file
13
vendor/golang.org/x/net/internal/socket/cmsghdr_bsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
||||
14
vendor/golang.org/x/net/internal/socket/cmsghdr_linux_32bit.go
generated
vendored
Normal file
14
vendor/golang.org/x/net/internal/socket/cmsghdr_linux_32bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm mips mipsle 386
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
||||
14
vendor/golang.org/x/net/internal/socket/cmsghdr_linux_64bit.go
generated
vendored
Normal file
14
vendor/golang.org/x/net/internal/socket/cmsghdr_linux_64bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm64 amd64 ppc64 ppc64le mips64 mips64le riscv64 s390x
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint64(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
||||
14
vendor/golang.org/x/net/internal/socket/cmsghdr_solaris_64bit.go
generated
vendored
Normal file
14
vendor/golang.org/x/net/internal/socket/cmsghdr_solaris_64bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64
|
||||
// +build solaris
|
||||
|
||||
package socket
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {
|
||||
h.Len = uint32(l)
|
||||
h.Level = int32(lvl)
|
||||
h.Type = int32(typ)
|
||||
}
|
||||
17
vendor/golang.org/x/net/internal/socket/cmsghdr_stub.go
generated
vendored
Normal file
17
vendor/golang.org/x/net/internal/socket/cmsghdr_stub.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||
|
||||
package socket
|
||||
|
||||
type cmsghdr struct{}
|
||||
|
||||
const sizeofCmsghdr = 0
|
||||
|
||||
func (h *cmsghdr) len() int { return 0 }
|
||||
func (h *cmsghdr) lvl() int { return 0 }
|
||||
func (h *cmsghdr) typ() int { return 0 }
|
||||
|
||||
func (h *cmsghdr) set(l, lvl, typ int) {}
|
||||
7
vendor/golang.org/x/net/internal/socket/empty.s
generated
vendored
Normal file
7
vendor/golang.org/x/net/internal/socket/empty.s
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin,go1.12
|
||||
|
||||
// This exists solely so we can linkname in symbols from syscall.
|
||||
31
vendor/golang.org/x/net/internal/socket/error_unix.go
generated
vendored
Normal file
31
vendor/golang.org/x/net/internal/socket/error_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package socket
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
errEAGAIN error = syscall.EAGAIN
|
||||
errEINVAL error = syscall.EINVAL
|
||||
errENOENT error = syscall.ENOENT
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent allocations
|
||||
// at runtime.
|
||||
func errnoErr(errno syscall.Errno) error {
|
||||
switch errno {
|
||||
case 0:
|
||||
return nil
|
||||
case syscall.EAGAIN:
|
||||
return errEAGAIN
|
||||
case syscall.EINVAL:
|
||||
return errEINVAL
|
||||
case syscall.ENOENT:
|
||||
return errENOENT
|
||||
}
|
||||
return errno
|
||||
}
|
||||
26
vendor/golang.org/x/net/internal/socket/error_windows.go
generated
vendored
Normal file
26
vendor/golang.org/x/net/internal/socket/error_windows.go
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
errERROR_IO_PENDING error = syscall.ERROR_IO_PENDING
|
||||
errEINVAL error = syscall.EINVAL
|
||||
)
|
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent allocations
|
||||
// at runtime.
|
||||
func errnoErr(errno syscall.Errno) error {
|
||||
switch errno {
|
||||
case 0:
|
||||
return nil
|
||||
case syscall.ERROR_IO_PENDING:
|
||||
return errERROR_IO_PENDING
|
||||
case syscall.EINVAL:
|
||||
return errEINVAL
|
||||
}
|
||||
return errno
|
||||
}
|
||||
19
vendor/golang.org/x/net/internal/socket/iovec_32bit.go
generated
vendored
Normal file
19
vendor/golang.org/x/net/internal/socket/iovec_32bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm mips mipsle 386
|
||||
// +build darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (v *iovec) set(b []byte) {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = (*byte)(unsafe.Pointer(&b[0]))
|
||||
v.Len = uint32(l)
|
||||
}
|
||||
19
vendor/golang.org/x/net/internal/socket/iovec_64bit.go
generated
vendored
Normal file
19
vendor/golang.org/x/net/internal/socket/iovec_64bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm64 amd64 ppc64 ppc64le mips64 mips64le riscv64 s390x
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (v *iovec) set(b []byte) {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = (*byte)(unsafe.Pointer(&b[0]))
|
||||
v.Len = uint64(l)
|
||||
}
|
||||
19
vendor/golang.org/x/net/internal/socket/iovec_solaris_64bit.go
generated
vendored
Normal file
19
vendor/golang.org/x/net/internal/socket/iovec_solaris_64bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64
|
||||
// +build solaris
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (v *iovec) set(b []byte) {
|
||||
l := len(b)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
v.Base = (*int8)(unsafe.Pointer(&b[0]))
|
||||
v.Len = uint64(l)
|
||||
}
|
||||
11
vendor/golang.org/x/net/internal/socket/iovec_stub.go
generated
vendored
Normal file
11
vendor/golang.org/x/net/internal/socket/iovec_stub.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||
|
||||
package socket
|
||||
|
||||
type iovec struct{}
|
||||
|
||||
func (v *iovec) set(b []byte) {}
|
||||
21
vendor/golang.org/x/net/internal/socket/mmsghdr_stub.go
generated
vendored
Normal file
21
vendor/golang.org/x/net/internal/socket/mmsghdr_stub.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !aix,!linux,!netbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "net"
|
||||
|
||||
type mmsghdr struct{}
|
||||
|
||||
type mmsghdrs []mmsghdr
|
||||
|
||||
func (hs mmsghdrs) pack(ms []Message, parseFn func([]byte, string) (net.Addr, error), marshalFn func(net.Addr) []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs mmsghdrs) unpack(ms []Message, parseFn func([]byte, string) (net.Addr, error), hint string) error {
|
||||
return nil
|
||||
}
|
||||
42
vendor/golang.org/x/net/internal/socket/mmsghdr_unix.go
generated
vendored
Normal file
42
vendor/golang.org/x/net/internal/socket/mmsghdr_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix linux netbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "net"
|
||||
|
||||
type mmsghdrs []mmsghdr
|
||||
|
||||
func (hs mmsghdrs) pack(ms []Message, parseFn func([]byte, string) (net.Addr, error), marshalFn func(net.Addr) []byte) error {
|
||||
for i := range hs {
|
||||
vs := make([]iovec, len(ms[i].Buffers))
|
||||
var sa []byte
|
||||
if parseFn != nil {
|
||||
sa = make([]byte, sizeofSockaddrInet6)
|
||||
}
|
||||
if marshalFn != nil {
|
||||
sa = marshalFn(ms[i].Addr)
|
||||
}
|
||||
hs[i].Hdr.pack(vs, ms[i].Buffers, ms[i].OOB, sa)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hs mmsghdrs) unpack(ms []Message, parseFn func([]byte, string) (net.Addr, error), hint string) error {
|
||||
for i := range hs {
|
||||
ms[i].N = int(hs[i].Len)
|
||||
ms[i].NN = hs[i].Hdr.controllen()
|
||||
ms[i].Flags = hs[i].Hdr.flags()
|
||||
if parseFn != nil {
|
||||
var err error
|
||||
ms[i].Addr, err = parseFn(hs[i].Hdr.name(), hint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
39
vendor/golang.org/x/net/internal/socket/msghdr_bsd.go
generated
vendored
Normal file
39
vendor/golang.org/x/net/internal/socket/msghdr_bsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
h.setIov(vs)
|
||||
if len(oob) > 0 {
|
||||
h.Control = (*byte)(unsafe.Pointer(&oob[0]))
|
||||
h.Controllen = uint32(len(oob))
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) name() []byte {
|
||||
if h.Name != nil && h.Namelen > 0 {
|
||||
return (*[sizeofSockaddrInet6]byte)(unsafe.Pointer(h.Name))[:h.Namelen]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Controllen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(h.Flags)
|
||||
}
|
||||
16
vendor/golang.org/x/net/internal/socket/msghdr_bsdvar.go
generated
vendored
Normal file
16
vendor/golang.org/x/net/internal/socket/msghdr_bsdvar.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd netbsd
|
||||
|
||||
package socket
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = int32(l)
|
||||
}
|
||||
36
vendor/golang.org/x/net/internal/socket/msghdr_linux.go
generated
vendored
Normal file
36
vendor/golang.org/x/net/internal/socket/msghdr_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
h.setIov(vs)
|
||||
if len(oob) > 0 {
|
||||
h.setControl(oob)
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) name() []byte {
|
||||
if h.Name != nil && h.Namelen > 0 {
|
||||
return (*[sizeofSockaddrInet6]byte)(unsafe.Pointer(h.Name))[:h.Namelen]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Controllen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(h.Flags)
|
||||
}
|
||||
24
vendor/golang.org/x/net/internal/socket/msghdr_linux_32bit.go
generated
vendored
Normal file
24
vendor/golang.org/x/net/internal/socket/msghdr_linux_32bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm mips mipsle 386
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = uint32(l)
|
||||
}
|
||||
|
||||
func (h *msghdr) setControl(b []byte) {
|
||||
h.Control = (*byte)(unsafe.Pointer(&b[0]))
|
||||
h.Controllen = uint32(len(b))
|
||||
}
|
||||
24
vendor/golang.org/x/net/internal/socket/msghdr_linux_64bit.go
generated
vendored
Normal file
24
vendor/golang.org/x/net/internal/socket/msghdr_linux_64bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build arm64 amd64 ppc64 ppc64le mips64 mips64le riscv64 s390x
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = uint64(l)
|
||||
}
|
||||
|
||||
func (h *msghdr) setControl(b []byte) {
|
||||
h.Control = (*byte)(unsafe.Pointer(&b[0]))
|
||||
h.Controllen = uint64(len(b))
|
||||
}
|
||||
14
vendor/golang.org/x/net/internal/socket/msghdr_openbsd.go
generated
vendored
Normal file
14
vendor/golang.org/x/net/internal/socket/msghdr_openbsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
func (h *msghdr) setIov(vs []iovec) {
|
||||
l := len(vs)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = uint32(l)
|
||||
}
|
||||
36
vendor/golang.org/x/net/internal/socket/msghdr_solaris_64bit.go
generated
vendored
Normal file
36
vendor/golang.org/x/net/internal/socket/msghdr_solaris_64bit.go
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build amd64
|
||||
// +build solaris
|
||||
|
||||
package socket
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {
|
||||
for i := range vs {
|
||||
vs[i].set(bs[i])
|
||||
}
|
||||
if len(vs) > 0 {
|
||||
h.Iov = &vs[0]
|
||||
h.Iovlen = int32(len(vs))
|
||||
}
|
||||
if len(oob) > 0 {
|
||||
h.Accrights = (*int8)(unsafe.Pointer(&oob[0]))
|
||||
h.Accrightslen = int32(len(oob))
|
||||
}
|
||||
if sa != nil {
|
||||
h.Name = (*byte)(unsafe.Pointer(&sa[0]))
|
||||
h.Namelen = uint32(len(sa))
|
||||
}
|
||||
}
|
||||
|
||||
func (h *msghdr) controllen() int {
|
||||
return int(h.Accrightslen)
|
||||
}
|
||||
|
||||
func (h *msghdr) flags() int {
|
||||
return int(NativeEndian.Uint32(h.Pad_cgo_2[:]))
|
||||
}
|
||||
14
vendor/golang.org/x/net/internal/socket/msghdr_stub.go
generated
vendored
Normal file
14
vendor/golang.org/x/net/internal/socket/msghdr_stub.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris
|
||||
|
||||
package socket
|
||||
|
||||
type msghdr struct{}
|
||||
|
||||
func (h *msghdr) pack(vs []iovec, bs [][]byte, oob []byte, sa []byte) {}
|
||||
func (h *msghdr) name() []byte { return nil }
|
||||
func (h *msghdr) controllen() int { return 0 }
|
||||
func (h *msghdr) flags() int { return 0 }
|
||||
12
vendor/golang.org/x/net/internal/socket/norace.go
generated
vendored
Normal file
12
vendor/golang.org/x/net/internal/socket/norace.go
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !race
|
||||
|
||||
package socket
|
||||
|
||||
func (m *Message) raceRead() {
|
||||
}
|
||||
func (m *Message) raceWrite() {
|
||||
}
|
||||
37
vendor/golang.org/x/net/internal/socket/race.go
generated
vendored
Normal file
37
vendor/golang.org/x/net/internal/socket/race.go
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build race
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// This package reads and writes the Message buffers using a
|
||||
// direct system call, which the race detector can't see.
|
||||
// These functions tell the race detector what is going on during the syscall.
|
||||
|
||||
func (m *Message) raceRead() {
|
||||
for _, b := range m.Buffers {
|
||||
if len(b) > 0 {
|
||||
runtime.RaceReadRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
if b := m.OOB; len(b) > 0 {
|
||||
runtime.RaceReadRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
func (m *Message) raceWrite() {
|
||||
for _, b := range m.Buffers {
|
||||
if len(b) > 0 {
|
||||
runtime.RaceWriteRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
if b := m.OOB; len(b) > 0 {
|
||||
runtime.RaceWriteRange(unsafe.Pointer(&b[0]), len(b))
|
||||
}
|
||||
}
|
||||
64
vendor/golang.org/x/net/internal/socket/rawconn.go
generated
vendored
Normal file
64
vendor/golang.org/x/net/internal/socket/rawconn.go
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// A Conn represents a raw connection.
|
||||
type Conn struct {
|
||||
network string
|
||||
c syscall.RawConn
|
||||
}
|
||||
|
||||
// NewConn returns a new raw connection.
|
||||
func NewConn(c net.Conn) (*Conn, error) {
|
||||
var err error
|
||||
var cc Conn
|
||||
switch c := c.(type) {
|
||||
case *net.TCPConn:
|
||||
cc.network = "tcp"
|
||||
cc.c, err = c.SyscallConn()
|
||||
case *net.UDPConn:
|
||||
cc.network = "udp"
|
||||
cc.c, err = c.SyscallConn()
|
||||
case *net.IPConn:
|
||||
cc.network = "ip"
|
||||
cc.c, err = c.SyscallConn()
|
||||
default:
|
||||
return nil, errors.New("unknown connection type")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cc, nil
|
||||
}
|
||||
|
||||
func (o *Option) get(c *Conn, b []byte) (int, error) {
|
||||
var operr error
|
||||
var n int
|
||||
fn := func(s uintptr) {
|
||||
n, operr = getsockopt(s, o.Level, o.Name, b)
|
||||
}
|
||||
if err := c.c.Control(fn); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, os.NewSyscallError("getsockopt", operr)
|
||||
}
|
||||
|
||||
func (o *Option) set(c *Conn, b []byte) error {
|
||||
var operr error
|
||||
fn := func(s uintptr) {
|
||||
operr = setsockopt(s, o.Level, o.Name, b)
|
||||
}
|
||||
if err := c.c.Control(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.NewSyscallError("setsockopt", operr)
|
||||
}
|
||||
79
vendor/golang.org/x/net/internal/socket/rawconn_mmsg.go
generated
vendored
Normal file
79
vendor/golang.org/x/net/internal/socket/rawconn_mmsg.go
generated
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (c *Conn) recvMsgs(ms []Message, flags int) (int, error) {
|
||||
for i := range ms {
|
||||
ms[i].raceWrite()
|
||||
}
|
||||
hs := make(mmsghdrs, len(ms))
|
||||
var parseFn func([]byte, string) (net.Addr, error)
|
||||
if c.network != "tcp" {
|
||||
parseFn = parseInetAddr
|
||||
}
|
||||
if err := hs.pack(ms, parseFn, nil); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var operr error
|
||||
var n int
|
||||
fn := func(s uintptr) bool {
|
||||
n, operr = recvmmsg(s, hs, flags)
|
||||
if operr == syscall.EAGAIN {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if err := c.c.Read(fn); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if operr != nil {
|
||||
return n, os.NewSyscallError("recvmmsg", operr)
|
||||
}
|
||||
if err := hs[:n].unpack(ms[:n], parseFn, c.network); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (c *Conn) sendMsgs(ms []Message, flags int) (int, error) {
|
||||
for i := range ms {
|
||||
ms[i].raceRead()
|
||||
}
|
||||
hs := make(mmsghdrs, len(ms))
|
||||
var marshalFn func(net.Addr) []byte
|
||||
if c.network != "tcp" {
|
||||
marshalFn = marshalInetAddr
|
||||
}
|
||||
if err := hs.pack(ms, nil, marshalFn); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var operr error
|
||||
var n int
|
||||
fn := func(s uintptr) bool {
|
||||
n, operr = sendmmsg(s, hs, flags)
|
||||
if operr == syscall.EAGAIN {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if err := c.c.Write(fn); err != nil {
|
||||
return n, err
|
||||
}
|
||||
if operr != nil {
|
||||
return n, os.NewSyscallError("sendmmsg", operr)
|
||||
}
|
||||
if err := hs[:n].unpack(ms[:n], nil, ""); err != nil {
|
||||
return n, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
78
vendor/golang.org/x/net/internal/socket/rawconn_msg.go
generated
vendored
Normal file
78
vendor/golang.org/x/net/internal/socket/rawconn_msg.go
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris windows
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func (c *Conn) recvMsg(m *Message, flags int) error {
|
||||
m.raceWrite()
|
||||
var h msghdr
|
||||
vs := make([]iovec, len(m.Buffers))
|
||||
var sa []byte
|
||||
if c.network != "tcp" {
|
||||
sa = make([]byte, sizeofSockaddrInet6)
|
||||
}
|
||||
h.pack(vs, m.Buffers, m.OOB, sa)
|
||||
var operr error
|
||||
var n int
|
||||
fn := func(s uintptr) bool {
|
||||
n, operr = recvmsg(s, &h, flags)
|
||||
if operr == syscall.EAGAIN {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if err := c.c.Read(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
if operr != nil {
|
||||
return os.NewSyscallError("recvmsg", operr)
|
||||
}
|
||||
if c.network != "tcp" {
|
||||
var err error
|
||||
m.Addr, err = parseInetAddr(sa[:], c.network)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
m.N = n
|
||||
m.NN = h.controllen()
|
||||
m.Flags = h.flags()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Conn) sendMsg(m *Message, flags int) error {
|
||||
m.raceRead()
|
||||
var h msghdr
|
||||
vs := make([]iovec, len(m.Buffers))
|
||||
var sa []byte
|
||||
if m.Addr != nil {
|
||||
sa = marshalInetAddr(m.Addr)
|
||||
}
|
||||
h.pack(vs, m.Buffers, m.OOB, sa)
|
||||
var operr error
|
||||
var n int
|
||||
fn := func(s uintptr) bool {
|
||||
n, operr = sendmsg(s, &h, flags)
|
||||
if operr == syscall.EAGAIN {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if err := c.c.Write(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
if operr != nil {
|
||||
return os.NewSyscallError("sendmsg", operr)
|
||||
}
|
||||
m.N = n
|
||||
m.NN = len(m.OOB)
|
||||
return nil
|
||||
}
|
||||
15
vendor/golang.org/x/net/internal/socket/rawconn_nommsg.go
generated
vendored
Normal file
15
vendor/golang.org/x/net/internal/socket/rawconn_nommsg.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package socket
|
||||
|
||||
func (c *Conn) recvMsgs(ms []Message, flags int) (int, error) {
|
||||
return 0, errNotImplemented
|
||||
}
|
||||
|
||||
func (c *Conn) sendMsgs(ms []Message, flags int) (int, error) {
|
||||
return 0, errNotImplemented
|
||||
}
|
||||
15
vendor/golang.org/x/net/internal/socket/rawconn_nomsg.go
generated
vendored
Normal file
15
vendor/golang.org/x/net/internal/socket/rawconn_nomsg.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !aix,!darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris,!windows
|
||||
|
||||
package socket
|
||||
|
||||
func (c *Conn) recvMsg(m *Message, flags int) error {
|
||||
return errNotImplemented
|
||||
}
|
||||
|
||||
func (c *Conn) sendMsg(m *Message, flags int) error {
|
||||
return errNotImplemented
|
||||
}
|
||||
288
vendor/golang.org/x/net/internal/socket/socket.go
generated
vendored
Normal file
288
vendor/golang.org/x/net/internal/socket/socket.go
generated
vendored
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package socket provides a portable interface for socket system
|
||||
// calls.
|
||||
package socket // import "golang.org/x/net/internal/socket"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH)
|
||||
|
||||
// An Option represents a sticky socket option.
|
||||
type Option struct {
|
||||
Level int // level
|
||||
Name int // name; must be equal or greater than 1
|
||||
Len int // length of value in bytes; must be equal or greater than 1
|
||||
}
|
||||
|
||||
// Get reads a value for the option from the kernel.
|
||||
// It returns the number of bytes written into b.
|
||||
func (o *Option) Get(c *Conn, b []byte) (int, error) {
|
||||
if o.Name < 1 || o.Len < 1 {
|
||||
return 0, errors.New("invalid option")
|
||||
}
|
||||
if len(b) < o.Len {
|
||||
return 0, errors.New("short buffer")
|
||||
}
|
||||
return o.get(c, b)
|
||||
}
|
||||
|
||||
// GetInt returns an integer value for the option.
|
||||
//
|
||||
// The Len field of Option must be either 1 or 4.
|
||||
func (o *Option) GetInt(c *Conn) (int, error) {
|
||||
if o.Len != 1 && o.Len != 4 {
|
||||
return 0, errors.New("invalid option")
|
||||
}
|
||||
var b []byte
|
||||
var bb [4]byte
|
||||
if o.Len == 1 {
|
||||
b = bb[:1]
|
||||
} else {
|
||||
b = bb[:4]
|
||||
}
|
||||
n, err := o.get(c, b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n != o.Len {
|
||||
return 0, errors.New("invalid option length")
|
||||
}
|
||||
if o.Len == 1 {
|
||||
return int(b[0]), nil
|
||||
}
|
||||
return int(NativeEndian.Uint32(b[:4])), nil
|
||||
}
|
||||
|
||||
// Set writes the option and value to the kernel.
|
||||
func (o *Option) Set(c *Conn, b []byte) error {
|
||||
if o.Name < 1 || o.Len < 1 {
|
||||
return errors.New("invalid option")
|
||||
}
|
||||
if len(b) < o.Len {
|
||||
return errors.New("short buffer")
|
||||
}
|
||||
return o.set(c, b)
|
||||
}
|
||||
|
||||
// SetInt writes the option and value to the kernel.
|
||||
//
|
||||
// The Len field of Option must be either 1 or 4.
|
||||
func (o *Option) SetInt(c *Conn, v int) error {
|
||||
if o.Len != 1 && o.Len != 4 {
|
||||
return errors.New("invalid option")
|
||||
}
|
||||
var b []byte
|
||||
if o.Len == 1 {
|
||||
b = []byte{byte(v)}
|
||||
} else {
|
||||
var bb [4]byte
|
||||
NativeEndian.PutUint32(bb[:o.Len], uint32(v))
|
||||
b = bb[:4]
|
||||
}
|
||||
return o.set(c, b)
|
||||
}
|
||||
|
||||
func controlHeaderLen() int {
|
||||
return roundup(sizeofCmsghdr)
|
||||
}
|
||||
|
||||
func controlMessageLen(dataLen int) int {
|
||||
return roundup(sizeofCmsghdr) + dataLen
|
||||
}
|
||||
|
||||
// ControlMessageSpace returns the whole length of control message.
|
||||
func ControlMessageSpace(dataLen int) int {
|
||||
return roundup(sizeofCmsghdr) + roundup(dataLen)
|
||||
}
|
||||
|
||||
// A ControlMessage represents the head message in a stream of control
|
||||
// messages.
|
||||
//
|
||||
// A control message comprises of a header, data and a few padding
|
||||
// fields to conform to the interface to the kernel.
|
||||
//
|
||||
// See RFC 3542 for further information.
|
||||
type ControlMessage []byte
|
||||
|
||||
// Data returns the data field of the control message at the head on
|
||||
// m.
|
||||
func (m ControlMessage) Data(dataLen int) []byte {
|
||||
l := controlHeaderLen()
|
||||
if len(m) < l || len(m) < l+dataLen {
|
||||
return nil
|
||||
}
|
||||
return m[l : l+dataLen]
|
||||
}
|
||||
|
||||
// Next returns the control message at the next on m.
|
||||
//
|
||||
// Next works only for standard control messages.
|
||||
func (m ControlMessage) Next(dataLen int) ControlMessage {
|
||||
l := ControlMessageSpace(dataLen)
|
||||
if len(m) < l {
|
||||
return nil
|
||||
}
|
||||
return m[l:]
|
||||
}
|
||||
|
||||
// MarshalHeader marshals the header fields of the control message at
|
||||
// the head on m.
|
||||
func (m ControlMessage) MarshalHeader(lvl, typ, dataLen int) error {
|
||||
if len(m) < controlHeaderLen() {
|
||||
return errors.New("short message")
|
||||
}
|
||||
h := (*cmsghdr)(unsafe.Pointer(&m[0]))
|
||||
h.set(controlMessageLen(dataLen), lvl, typ)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseHeader parses and returns the header fields of the control
|
||||
// message at the head on m.
|
||||
func (m ControlMessage) ParseHeader() (lvl, typ, dataLen int, err error) {
|
||||
l := controlHeaderLen()
|
||||
if len(m) < l {
|
||||
return 0, 0, 0, errors.New("short message")
|
||||
}
|
||||
h := (*cmsghdr)(unsafe.Pointer(&m[0]))
|
||||
return h.lvl(), h.typ(), int(uint64(h.len()) - uint64(l)), nil
|
||||
}
|
||||
|
||||
// Marshal marshals the control message at the head on m, and returns
|
||||
// the next control message.
|
||||
func (m ControlMessage) Marshal(lvl, typ int, data []byte) (ControlMessage, error) {
|
||||
l := len(data)
|
||||
if len(m) < ControlMessageSpace(l) {
|
||||
return nil, errors.New("short message")
|
||||
}
|
||||
h := (*cmsghdr)(unsafe.Pointer(&m[0]))
|
||||
h.set(controlMessageLen(l), lvl, typ)
|
||||
if l > 0 {
|
||||
copy(m.Data(l), data)
|
||||
}
|
||||
return m.Next(l), nil
|
||||
}
|
||||
|
||||
// Parse parses m as a single or multiple control messages.
|
||||
//
|
||||
// Parse works for both standard and compatible messages.
|
||||
func (m ControlMessage) Parse() ([]ControlMessage, error) {
|
||||
var ms []ControlMessage
|
||||
for len(m) >= controlHeaderLen() {
|
||||
h := (*cmsghdr)(unsafe.Pointer(&m[0]))
|
||||
l := h.len()
|
||||
if l <= 0 {
|
||||
return nil, errors.New("invalid header length")
|
||||
}
|
||||
if uint64(l) < uint64(controlHeaderLen()) {
|
||||
return nil, errors.New("invalid message length")
|
||||
}
|
||||
if uint64(l) > uint64(len(m)) {
|
||||
return nil, errors.New("short buffer")
|
||||
}
|
||||
// On message reception:
|
||||
//
|
||||
// |<- ControlMessageSpace --------------->|
|
||||
// |<- controlMessageLen ---------->| |
|
||||
// |<- controlHeaderLen ->| | |
|
||||
// +---------------+------+---------+------+
|
||||
// | Header | PadH | Data | PadD |
|
||||
// +---------------+------+---------+------+
|
||||
//
|
||||
// On compatible message reception:
|
||||
//
|
||||
// | ... |<- controlMessageLen ----------->|
|
||||
// | ... |<- controlHeaderLen ->| |
|
||||
// +-----+---------------+------+----------+
|
||||
// | ... | Header | PadH | Data |
|
||||
// +-----+---------------+------+----------+
|
||||
ms = append(ms, ControlMessage(m[:l]))
|
||||
ll := l - controlHeaderLen()
|
||||
if len(m) >= ControlMessageSpace(ll) {
|
||||
m = m[ControlMessageSpace(ll):]
|
||||
} else {
|
||||
m = m[controlMessageLen(ll):]
|
||||
}
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
// NewControlMessage returns a new stream of control messages.
|
||||
func NewControlMessage(dataLen []int) ControlMessage {
|
||||
var l int
|
||||
for i := range dataLen {
|
||||
l += ControlMessageSpace(dataLen[i])
|
||||
}
|
||||
return make([]byte, l)
|
||||
}
|
||||
|
||||
// A Message represents an IO message.
|
||||
type Message struct {
|
||||
// When writing, the Buffers field must contain at least one
|
||||
// byte to write.
|
||||
// When reading, the Buffers field will always contain a byte
|
||||
// to read.
|
||||
Buffers [][]byte
|
||||
|
||||
// OOB contains protocol-specific control or miscellaneous
|
||||
// ancillary data known as out-of-band data.
|
||||
OOB []byte
|
||||
|
||||
// Addr specifies a destination address when writing.
|
||||
// It can be nil when the underlying protocol of the raw
|
||||
// connection uses connection-oriented communication.
|
||||
// After a successful read, it may contain the source address
|
||||
// on the received packet.
|
||||
Addr net.Addr
|
||||
|
||||
N int // # of bytes read or written from/to Buffers
|
||||
NN int // # of bytes read or written from/to OOB
|
||||
Flags int // protocol-specific information on the received message
|
||||
}
|
||||
|
||||
// RecvMsg wraps recvmsg system call.
|
||||
//
|
||||
// The provided flags is a set of platform-dependent flags, such as
|
||||
// syscall.MSG_PEEK.
|
||||
func (c *Conn) RecvMsg(m *Message, flags int) error {
|
||||
return c.recvMsg(m, flags)
|
||||
}
|
||||
|
||||
// SendMsg wraps sendmsg system call.
|
||||
//
|
||||
// The provided flags is a set of platform-dependent flags, such as
|
||||
// syscall.MSG_DONTROUTE.
|
||||
func (c *Conn) SendMsg(m *Message, flags int) error {
|
||||
return c.sendMsg(m, flags)
|
||||
}
|
||||
|
||||
// RecvMsgs wraps recvmmsg system call.
|
||||
//
|
||||
// It returns the number of processed messages.
|
||||
//
|
||||
// The provided flags is a set of platform-dependent flags, such as
|
||||
// syscall.MSG_PEEK.
|
||||
//
|
||||
// Only Linux supports this.
|
||||
func (c *Conn) RecvMsgs(ms []Message, flags int) (int, error) {
|
||||
return c.recvMsgs(ms, flags)
|
||||
}
|
||||
|
||||
// SendMsgs wraps sendmmsg system call.
|
||||
//
|
||||
// It returns the number of processed messages.
|
||||
//
|
||||
// The provided flags is a set of platform-dependent flags, such as
|
||||
// syscall.MSG_DONTROUTE.
|
||||
//
|
||||
// Only Linux supports this.
|
||||
func (c *Conn) SendMsgs(ms []Message, flags int) (int, error) {
|
||||
return c.sendMsgs(ms, flags)
|
||||
}
|
||||
33
vendor/golang.org/x/net/internal/socket/sys.go
generated
vendored
Normal file
33
vendor/golang.org/x/net/internal/socket/sys.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
// NativeEndian is the machine native endian implementation of
|
||||
// ByteOrder.
|
||||
NativeEndian binary.ByteOrder
|
||||
|
||||
kernelAlign int
|
||||
)
|
||||
|
||||
func init() {
|
||||
i := uint32(1)
|
||||
b := (*[4]byte)(unsafe.Pointer(&i))
|
||||
if b[0] == 1 {
|
||||
NativeEndian = binary.LittleEndian
|
||||
} else {
|
||||
NativeEndian = binary.BigEndian
|
||||
}
|
||||
kernelAlign = probeProtocolStack()
|
||||
}
|
||||
|
||||
func roundup(l int) int {
|
||||
return (l + kernelAlign - 1) &^ (kernelAlign - 1)
|
||||
}
|
||||
15
vendor/golang.org/x/net/internal/socket/sys_bsd.go
generated
vendored
Normal file
15
vendor/golang.org/x/net/internal/socket/sys_bsd.go
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
func recvmmsg(s uintptr, hs []mmsghdr, flags int) (int, error) {
|
||||
return 0, errNotImplemented
|
||||
}
|
||||
|
||||
func sendmmsg(s uintptr, hs []mmsghdr, flags int) (int, error) {
|
||||
return 0, errNotImplemented
|
||||
}
|
||||
23
vendor/golang.org/x/net/internal/socket/sys_bsdvar.go
generated
vendored
Normal file
23
vendor/golang.org/x/net/internal/socket/sys_bsdvar.go
generated
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix freebsd netbsd openbsd
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func probeProtocolStack() int {
|
||||
if (runtime.GOOS == "netbsd" || runtime.GOOS == "openbsd") && runtime.GOARCH == "arm" {
|
||||
return 8
|
||||
}
|
||||
if runtime.GOOS == "aix" {
|
||||
return 1
|
||||
}
|
||||
var p uintptr
|
||||
return int(unsafe.Sizeof(p))
|
||||
}
|
||||
17
vendor/golang.org/x/net/internal/socket/sys_const_unix.go
generated
vendored
Normal file
17
vendor/golang.org/x/net/internal/socket/sys_const_unix.go
generated
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris
|
||||
|
||||
package socket
|
||||
|
||||
import "golang.org/x/sys/unix"
|
||||
|
||||
const (
|
||||
sysAF_UNSPEC = unix.AF_UNSPEC
|
||||
sysAF_INET = unix.AF_INET
|
||||
sysAF_INET6 = unix.AF_INET6
|
||||
|
||||
sysSOCK_RAW = unix.SOCK_RAW
|
||||
)
|
||||
7
vendor/golang.org/x/net/internal/socket/sys_darwin.go
generated
vendored
Normal file
7
vendor/golang.org/x/net/internal/socket/sys_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
func probeProtocolStack() int { return 4 }
|
||||
32
vendor/golang.org/x/net/internal/socket/sys_dragonfly.go
generated
vendored
Normal file
32
vendor/golang.org/x/net/internal/socket/sys_dragonfly.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// See version list in https://github.com/DragonFlyBSD/DragonFlyBSD/blob/master/sys/sys/param.h
|
||||
var (
|
||||
osreldateOnce sync.Once
|
||||
osreldate uint32
|
||||
)
|
||||
|
||||
// First __DragonFly_version after September 2019 ABI changes
|
||||
// http://lists.dragonflybsd.org/pipermail/users/2019-September/358280.html
|
||||
const _dragonflyABIChangeVersion = 500705
|
||||
|
||||
func probeProtocolStack() int {
|
||||
osreldateOnce.Do(func() { osreldate, _ = syscall.SysctlUint32("kern.osreldate") })
|
||||
var p uintptr
|
||||
if int(unsafe.Sizeof(p)) == 8 && osreldate >= _dragonflyABIChangeVersion {
|
||||
return int(unsafe.Sizeof(p))
|
||||
}
|
||||
// 64-bit Dragonfly before the September 2019 ABI changes still requires
|
||||
// 32-bit aligned access to network subsystem.
|
||||
return 4
|
||||
}
|
||||
33
vendor/golang.org/x/net/internal/socket/sys_go1_11_darwin.go
generated
vendored
Normal file
33
vendor/golang.org/x/net/internal/socket/sys_go1_11_darwin.go
generated
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.12
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
|
||||
l := uint32(len(b))
|
||||
_, _, errno := syscall.Syscall6(syscall.SYS_GETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&l)), 0)
|
||||
return int(l), errnoErr(errno)
|
||||
}
|
||||
|
||||
func setsockopt(s uintptr, level, name int, b []byte) error {
|
||||
_, _, errno := syscall.Syscall6(syscall.SYS_SETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0)
|
||||
return errnoErr(errno)
|
||||
}
|
||||
|
||||
func recvmsg(s uintptr, h *msghdr, flags int) (int, error) {
|
||||
n, _, errno := syscall.Syscall(syscall.SYS_RECVMSG, s, uintptr(unsafe.Pointer(h)), uintptr(flags))
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
|
||||
func sendmsg(s uintptr, h *msghdr, flags int) (int, error) {
|
||||
n, _, errno := syscall.Syscall(syscall.SYS_SENDMSG, s, uintptr(unsafe.Pointer(h)), uintptr(flags))
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
42
vendor/golang.org/x/net/internal/socket/sys_linkname.go
generated
vendored
Normal file
42
vendor/golang.org/x/net/internal/socket/sys_linkname.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build aix go1.12,darwin
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//go:linkname syscall_getsockopt syscall.getsockopt
|
||||
func syscall_getsockopt(s int, level int, name int, val unsafe.Pointer, vallen *uint32) error
|
||||
|
||||
func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
|
||||
l := uint32(len(b))
|
||||
err := syscall_getsockopt(int(s), level, name, unsafe.Pointer(&b[0]), &l)
|
||||
return int(l), err
|
||||
}
|
||||
|
||||
//go:linkname syscall_setsockopt syscall.setsockopt
|
||||
func syscall_setsockopt(s int, level int, name int, val unsafe.Pointer, vallen uintptr) error
|
||||
|
||||
func setsockopt(s uintptr, level, name int, b []byte) error {
|
||||
return syscall_setsockopt(int(s), level, name, unsafe.Pointer(&b[0]), uintptr(len(b)))
|
||||
}
|
||||
|
||||
//go:linkname syscall_recvmsg syscall.recvmsg
|
||||
func syscall_recvmsg(s int, msg *syscall.Msghdr, flags int) (n int, err error)
|
||||
|
||||
func recvmsg(s uintptr, h *msghdr, flags int) (int, error) {
|
||||
return syscall_recvmsg(int(s), (*syscall.Msghdr)(unsafe.Pointer(h)), flags)
|
||||
}
|
||||
|
||||
//go:linkname syscall_sendmsg syscall.sendmsg
|
||||
func syscall_sendmsg(s int, msg *syscall.Msghdr, flags int) (n int, err error)
|
||||
|
||||
func sendmsg(s uintptr, h *msghdr, flags int) (int, error) {
|
||||
return syscall_sendmsg(int(s), (*syscall.Msghdr)(unsafe.Pointer(h)), flags)
|
||||
}
|
||||
27
vendor/golang.org/x/net/internal/socket/sys_linux.go
generated
vendored
Normal file
27
vendor/golang.org/x/net/internal/socket/sys_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build linux,!s390x,!386
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func probeProtocolStack() int {
|
||||
var p uintptr
|
||||
return int(unsafe.Sizeof(p))
|
||||
}
|
||||
|
||||
func recvmmsg(s uintptr, hs []mmsghdr, flags int) (int, error) {
|
||||
n, _, errno := syscall.Syscall6(sysRECVMMSG, s, uintptr(unsafe.Pointer(&hs[0])), uintptr(len(hs)), uintptr(flags), 0, 0)
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
|
||||
func sendmmsg(s uintptr, hs []mmsghdr, flags int) (int, error) {
|
||||
n, _, errno := syscall.Syscall6(sysSENDMMSG, s, uintptr(unsafe.Pointer(&hs[0])), uintptr(len(hs)), uintptr(flags), 0, 0)
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
55
vendor/golang.org/x/net/internal/socket/sys_linux_386.go
generated
vendored
Normal file
55
vendor/golang.org/x/net/internal/socket/sys_linux_386.go
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func probeProtocolStack() int { return 4 }
|
||||
|
||||
const (
|
||||
sysSETSOCKOPT = 0xe
|
||||
sysGETSOCKOPT = 0xf
|
||||
sysSENDMSG = 0x10
|
||||
sysRECVMSG = 0x11
|
||||
sysRECVMMSG = 0x13
|
||||
sysSENDMMSG = 0x14
|
||||
)
|
||||
|
||||
func socketcall(call, a0, a1, a2, a3, a4, a5 uintptr) (uintptr, syscall.Errno)
|
||||
func rawsocketcall(call, a0, a1, a2, a3, a4, a5 uintptr) (uintptr, syscall.Errno)
|
||||
|
||||
func getsockopt(s uintptr, level, name int, b []byte) (int, error) {
|
||||
l := uint32(len(b))
|
||||
_, errno := socketcall(sysGETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(unsafe.Pointer(&l)), 0)
|
||||
return int(l), errnoErr(errno)
|
||||
}
|
||||
|
||||
func setsockopt(s uintptr, level, name int, b []byte) error {
|
||||
_, errno := socketcall(sysSETSOCKOPT, s, uintptr(level), uintptr(name), uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0)
|
||||
return errnoErr(errno)
|
||||
}
|
||||
|
||||
func recvmsg(s uintptr, h *msghdr, flags int) (int, error) {
|
||||
n, errno := socketcall(sysRECVMSG, s, uintptr(unsafe.Pointer(h)), uintptr(flags), 0, 0, 0)
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
|
||||
func sendmsg(s uintptr, h *msghdr, flags int) (int, error) {
|
||||
n, errno := socketcall(sysSENDMSG, s, uintptr(unsafe.Pointer(h)), uintptr(flags), 0, 0, 0)
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
|
||||
func recvmmsg(s uintptr, hs []mmsghdr, flags int) (int, error) {
|
||||
n, errno := socketcall(sysRECVMMSG, s, uintptr(unsafe.Pointer(&hs[0])), uintptr(len(hs)), uintptr(flags), 0, 0)
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
|
||||
func sendmmsg(s uintptr, hs []mmsghdr, flags int) (int, error) {
|
||||
n, errno := socketcall(sysSENDMMSG, s, uintptr(unsafe.Pointer(&hs[0])), uintptr(len(hs)), uintptr(flags), 0, 0)
|
||||
return int(n), errnoErr(errno)
|
||||
}
|
||||
11
vendor/golang.org/x/net/internal/socket/sys_linux_386.s
generated
vendored
Normal file
11
vendor/golang.org/x/net/internal/socket/sys_linux_386.s
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·socketcall(SB),NOSPLIT,$0-36
|
||||
JMP syscall·socketcall(SB)
|
||||
|
||||
TEXT ·rawsocketcall(SB),NOSPLIT,$0-36
|
||||
JMP syscall·rawsocketcall(SB)
|
||||
10
vendor/golang.org/x/net/internal/socket/sys_linux_amd64.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/internal/socket/sys_linux_amd64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
sysRECVMMSG = 0x12b
|
||||
sysSENDMMSG = 0x133
|
||||
)
|
||||
10
vendor/golang.org/x/net/internal/socket/sys_linux_arm.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/internal/socket/sys_linux_arm.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
sysRECVMMSG = 0x16d
|
||||
sysSENDMMSG = 0x176
|
||||
)
|
||||
10
vendor/golang.org/x/net/internal/socket/sys_linux_arm64.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/internal/socket/sys_linux_arm64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
sysRECVMMSG = 0xf3
|
||||
sysSENDMMSG = 0x10d
|
||||
)
|
||||
10
vendor/golang.org/x/net/internal/socket/sys_linux_mips.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/internal/socket/sys_linux_mips.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
sysRECVMMSG = 0x10ef
|
||||
sysSENDMMSG = 0x10f7
|
||||
)
|
||||
10
vendor/golang.org/x/net/internal/socket/sys_linux_mips64.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/internal/socket/sys_linux_mips64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
sysRECVMMSG = 0x14ae
|
||||
sysSENDMMSG = 0x14b6
|
||||
)
|
||||
10
vendor/golang.org/x/net/internal/socket/sys_linux_mips64le.go
generated
vendored
Normal file
10
vendor/golang.org/x/net/internal/socket/sys_linux_mips64le.go
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socket
|
||||
|
||||
const (
|
||||
sysRECVMMSG = 0x14ae
|
||||
sysSENDMMSG = 0x14b6
|
||||
)
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue