dlna.lisp 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021
  1. (in-package :cl-user)
  2. (defpackage dlna-chad-music
  3. (:use :cl)
  4. (:import-from :woo.ev.event-loop
  5. :check-event-loop-running
  6. :with-event-loop
  7. :callbacks
  8. :*evloop*
  9. :*input-buffer*)
  10. (:import-from :alexandria
  11. :when-let
  12. :with-gensyms)
  13. (:import-from :woo.ev.util
  14. :define-c-callback
  15. :io-fd))
  16. (in-package :dlna-chad-music)
  17. (defparameter +sockaddr-in-size+ (cffi:foreign-type-size '(:struct wsock::sockaddr-in)))
  18. (defun set-sock-int-opt (fd level optname &optional (value 1))
  19. (cffi:with-foreign-object (optval :int)
  20. (setf (cffi:mem-aref optval :int) value)
  21. (when (= (wsock:setsockopt fd level optname optval (cffi:foreign-type-size :int)) -1)
  22. (error 'os-error
  23. :description "Cannot set socket option"
  24. :code (wsys:errno)))))
  25. (defun udp-socket (address port &key sockopt)
  26. (cffi:with-foreign-object (ai '(:pointer (:struct wsock:addrinfo)))
  27. (cffi:with-foreign-object (hints '(:struct wsock:addrinfo))
  28. (wsys:bzero hints (cffi:foreign-type-size '(:struct wsock:addrinfo)))
  29. (cffi:with-foreign-slots ((wsock::family wsock::socktype wsock::flags) hints (:struct wsock:addrinfo))
  30. (setf wsock::family (if (and (stringp address)
  31. (quri.domain:ipv6-addr-p address))
  32. wsock:+AF-INET6+
  33. wsock:+AF-INET+)
  34. wsock::socktype wsock:+SOCK-DGRAM+
  35. wsock::flags wsock:+AI-PASSIVE+))
  36. (let ((err (wsock:getaddrinfo (or address (cffi:null-pointer))
  37. (if port (write-to-string port) (cffi:null-pointer))
  38. hints ai)))
  39. (unless (= err 0)
  40. (error 'os-error
  41. :description "getaddrinfo() failed"
  42. :code err))))
  43. (let ((ai (cffi:mem-ref ai :pointer)))
  44. (cffi:with-foreign-slots ((wsock::family
  45. wsock::socktype
  46. wsock::protocol
  47. wsock::addr
  48. wsock::addrlen)
  49. ai
  50. (:struct wsock:addrinfo))
  51. (let ((fd (wsock:socket wsock::family wsock::socktype wsock::protocol)))
  52. (when (= fd -1)
  53. (error 'os-error
  54. :description (format nil "Cannot create udp socket (family=~S / socktype=~S / protocol=~S)"
  55. wsock::family
  56. wsock::socktype
  57. wsock::protocol)
  58. :code (wsys:errno)))
  59. (when sockopt (set-sock-int-opt fd wsock:+SOL-SOCKET+ sockopt))
  60. (when (= (wsock:bind fd wsock::addr wsock::addrlen) -1)
  61. (error 'os-error
  62. :description (format nil "Cannot bind fd to the address ~S" address)
  63. :code (wsys:errno)))
  64. (wsock:freeaddrinfo ai)
  65. fd)))))
  66. (defun join-multicast-group (fd address)
  67. (cffi:with-foreign-object (mreq '(:struct wsock:ip-mreq))
  68. (cffi:with-foreign-slots ((wsock::multiaddr wsock::interface) mreq (:struct wsock:ip-mreq))
  69. (setf wsock::multiaddr (wsock:inet-addr address)
  70. wsock::interface wsock:+inaddr-any+)
  71. (when (= (wsock:setsockopt fd wsock:+ipproto-ip+
  72. wsock:+ip-add-membership+ mreq
  73. (cffi:foreign-type-size '(:struct wsock:ip-mreq))) -1)
  74. (error 'os-error
  75. :description (format nil "Cannot join fd to multicast group ~A" address)
  76. :code (wsys:errno))))))
  77. (defvar *udp-name-len* 128)
  78. (defvar *udp-buffer-len* (* 1024 64))
  79. (defvar *udp-control-len* 1024)
  80. (defparameter *udp-message* nil)
  81. (defparameter *udp-name* nil)
  82. (defparameter *udp-iov* nil)
  83. (defparameter *udp-control* nil)
  84. (defparameter *udp-buffer* nil)
  85. (defparameter *udp-sockstring* nil)
  86. (defmacro with-udp-buffers (&body body)
  87. `(let ((*udp-name* (static-vectors:make-static-vector *udp-name-len*))
  88. (*udp-buffer* (static-vectors:make-static-vector *udp-buffer-len*))
  89. (*udp-control* (static-vectors:make-static-vector *udp-control-len*))
  90. (*udp-sockstring* (cffi:foreign-alloc :char :count wsock:+inet6-addrstrlen+)))
  91. (cffi:with-foreign-objects
  92. ((*udp-message* '(:struct wsock:msghdr))
  93. (*udp-iov* '(:struct wsock:iovec)))
  94. (cffi:with-foreign-slots ((wsock::name wsock::iov wsock::iovlen wsock::control wsock::controllen)
  95. *udp-message* (:struct wsock:msghdr))
  96. (cffi:with-foreign-slots ((wsock::base wsock::len) *udp-iov* (:struct wsock:iovec))
  97. (setf wsock::base (static-vectors:static-vector-pointer *udp-buffer*)
  98. wsock::len (length *udp-buffer*)
  99. wsock::name (static-vectors:static-vector-pointer *udp-name*)
  100. ;; namelen and controllen have to be set on all recvmsg
  101. wsock::iov *udp-iov*
  102. wsock::iovlen 1
  103. wsock::control (static-vectors:static-vector-pointer *udp-control*))
  104. (unwind-protect (progn ,@body)
  105. (static-vectors:free-static-vector *udp-name*)
  106. (static-vectors:free-static-vector *udp-buffer*)
  107. (static-vectors:free-static-vector *udp-control*)
  108. (cffi:foreign-free *udp-sockstring*)))))))
  109. (defun get-name-port (sockaddr)
  110. (let ((family (cffi:foreign-slot-value sockaddr '(:struct wsock:sockaddr-storage) 'wsock::family)))
  111. (declare (type fixnum family))
  112. (cond
  113. ((= family wsock:+AF-INET6+)
  114. (wsock:inet-ntop family (cffi:foreign-slot-pointer sockaddr '(:struct wsock:sockaddr-in6) 'wsock::addr)
  115. *udp-sockstring* wsock:+inet6-addrstrlen+)
  116. (values
  117. (cffi:foreign-string-to-lisp *udp-sockstring*)
  118. (swap-bytes:ntohs (cffi:foreign-slot-value sockaddr '(:struct wsock:sockaddr-in6) 'wsock::port))))
  119. ((= family wsock:+AF-INET+)
  120. (wsock:inet-ntop family (cffi:foreign-slot-pointer sockaddr '(:struct wsock:sockaddr-in) 'wsock::addr)
  121. *udp-sockstring* wsock:+inet6-addrstrlen+)
  122. (values
  123. (cffi:foreign-string-to-lisp *udp-sockstring*)
  124. (swap-bytes:ntohs (cffi:foreign-slot-value sockaddr '(:struct wsock:sockaddr-in) 'wsock::port))))
  125. (:otherwise (values nil nil)))))
  126. (defmacro with-sockaddr-in ((var addr port) &body body)
  127. `(cffi:with-foreign-object (,var '(:struct wsock::sockaddr-in))
  128. (cffi:with-foreign-slots ((wsock::family wsock::addr wsock::port) ,var (:struct wsock::sockaddr-in))
  129. (setf wsock::family wsock:+af-inet+
  130. wsock::addr (wsock:inet-addr ,addr)
  131. wsock::port (swap-bytes:htons ,port))
  132. ,@body)))
  133. (defun send-to (fd addr port data)
  134. (with-sockaddr-in (dest addr port)
  135. (let ((message (trivial-utf-8:string-to-utf-8-bytes data)))
  136. (cffi:with-pointer-to-vector-data (buffer data)
  137. (wsock:sendto fd buffer (length message) 0 dest +sockaddr-in-size+)))))
  138. (defun align-h (size)
  139. (declare (optimize (speed 3) (safety 0)))
  140. (declare (type (unsigned-byte 32) size))
  141. (let ((r (1- (the (integer 4 32) (cffi:foreign-type-size 'wsock::size-t)))))
  142. (logand (+ size r) (lognot r))))
  143. (defun align-d (size) (align-h size))
  144. (defun cmsg-next (msg cmsg)
  145. (cffi:with-foreign-slots ((wsock::control wsock::controllen) msg (:struct wsock::msghdr))
  146. (declare (type (unsigned-byte 32) wsock::controllen))
  147. (if (cffi:null-pointer-p cmsg)
  148. (if (>= wsock::controllen (the (unsigned-byte 32) (cffi:foreign-type-size '(:struct wsock:cmsghdr))))
  149. wsock::control (cffi:null-pointer))
  150. (let ((next (cffi:inc-pointer cmsg (align-h (the (unsigned-byte 32) (cffi:foreign-slot-value cmsg '(:struct wsock:cmsghdr) 'wsock::len))))))
  151. (if (> (cffi:pointer-address (cffi:inc-pointer next (align-d (cffi:foreign-type-size '(:struct wsock:cmsghdr)))))
  152. (cffi:pointer-address (cffi:inc-pointer wsock::control wsock::controllen)))
  153. (cffi:null-pointer)
  154. next)))))
  155. (defun cmsg-data (cmsg)
  156. (declare (optimize (speed 3) (safety 0)))
  157. (cffi:inc-pointer cmsg (align-d (cffi:foreign-type-size '(:struct wsock:cmsghdr)))))
  158. (defun parse-cmsg (level type data len)
  159. (cond
  160. ((and (= level wsock:+ipproto-ip+)
  161. (= type wsock:+ip-pktinfo+))
  162. (cffi:with-foreign-slots ((wsock::if-index wsock::spec-dst wsock::addr) data (:struct wsock::pktinfo))
  163. (list :iface-idx wsock::if-index :local-addr (wsock:inet-ntoa wsock::spec-dst) :dst-addr (wsock:inet-ntoa wsock::addr))))
  164. ((and (= level wsock:+ipproto-ip+)
  165. (= type wsock::+ip-recvorigdstaddr+))
  166. (cffi:with-foreign-slots ((wsock::family wsock::port wsock::addr) data (:struct wsock::sockaddr-in))
  167. (list :dst-family wsock::family :dst-port (swap-bytes:ntohs wsock::port) :dst-addr (wsock:inet-ntoa wsock::addr))))
  168. (t (list (intern (format nil "LEVEL-~a-TYPE-~a" level type) :keyword)
  169. (cffi:foreign-array-to-lisp data `(:array :unsigned-char ,len))))))
  170. (defun get-cmsgs (msg)
  171. (loop for cmsg = (cmsg-next msg (cffi:null-pointer)) then (cmsg-next msg cmsg)
  172. until (cffi:null-pointer-p cmsg)
  173. append (cffi:with-foreign-slots ((wsock::level wsock::type wsock::len) cmsg (:struct wsock:cmsghdr))
  174. (parse-cmsg wsock::level wsock::type (cmsg-data cmsg)
  175. (- wsock::len (align-d (cffi:foreign-type-size '(:struct wsock:cmsghdr))))))))
  176. (defun get-name-aux (message)
  177. (multiple-value-bind (addr port)
  178. (get-name-port (cffi:foreign-slot-value message '(:struct wsock:msghdr) 'wsock::name))
  179. (append (list :src-addr addr :src-port port) (get-cmsgs message))))
  180. (defun dump-message (message len)
  181. (cffi:with-foreign-slots ((wsock::name wsock::namelen wsock::iov wsock::control wsock::controllen)
  182. message (:struct wsock:msghdr))
  183. (cffi:with-foreign-slots ((wsock::base wsock::len) wsock::iov (:struct wsock:iovec))
  184. (format t "~A ~A~%~A ~A~%~A ~A~%"
  185. wsock::namelen (cffi:foreign-array-to-lisp wsock::name `(:array :unsigned-char ,wsock::namelen))
  186. len (cffi:foreign-string-to-lisp wsock::base :count len)
  187. wsock::controllen (cffi:foreign-array-to-lisp wsock::control `(:array :unsigned-char ,wsock::controllen))))))
  188. (define-c-callback udp-read-cb :void ((evloop :pointer) (watcher :pointer) (events :int))
  189. (declare (ignore evloop events))
  190. (let* ((fd (io-fd watcher))
  191. (read-cb (callbacks fd)))
  192. (setf (cffi:foreign-slot-value *udp-message* '(:struct wsock:msghdr) 'wsock::namelen) *udp-name-len*
  193. (cffi:foreign-slot-value *udp-message* '(:struct wsock:msghdr) 'wsock::controllen) *udp-control-len*)
  194. (let ((len (wsock:recvmsg fd *udp-message* 0)))
  195. (declare (type fixnum len))
  196. ;; (dump-message *udp-message* len)
  197. (if (= len -1)
  198. (let ((errno (wsys:errno)))
  199. (cond
  200. ;; Just to nothing
  201. ((or (= errno wsys:EWOULDBLOCK)
  202. (= errno wsys:EINTR)
  203. (= errno wsys:EAGAIN)))
  204. (t
  205. (vom:error "Unexpected error (Code: ~D)" errno))))
  206. (when read-cb
  207. (apply (the function read-cb) fd *udp-buffer* len (get-name-aux *udp-message*)))))))
  208. (defun udp-server (address port read-cb &key fd (sockopt wsock:+SO-REUSEADDR+) multicast)
  209. (check-event-loop-running)
  210. (let ((fd (or fd (udp-socket address port :sockopt sockopt)))
  211. (listener (cffi:foreign-alloc '(:struct lev:ev-io))))
  212. (when (= (wsys:set-fd-nonblock fd t) -1)
  213. (error 'os-error
  214. :description "Cannot set fd nonblock"
  215. :code (wsys:errno)))
  216. #+linux (set-sock-int-opt fd wsock:+ipproto-ip+ wsock:+ip-pktinfo+)
  217. (loop for address in multicast
  218. do (join-multicast-group fd address))
  219. (setf (callbacks fd) read-cb)
  220. (lev:ev-io-init listener 'udp-read-cb fd lev:+EV-READ+)
  221. (lev:ev-io-start *evloop* listener)
  222. listener))
  223. (defparameter +ssdp-multicast-group+ "239.255.255.250")
  224. (defparameter +ssdp-port+ 1900)
  225. (defvar *notify-ip-ttl* 4)
  226. (defvar *ssdp-listener* nil)
  227. (defvar *port* 50000)
  228. ;; Devices should wait a random interval less than 100 milliseconds before sending an initial set of advertisements in order to reduce the likelihood of network storms; this random interval should also be applied on occasions where the device obtains a new IP address or a new network interface is installed.
  229. ;; In addition, the device must re-send its advertisements periodically prior to expiration of the duration specified in the CACHE-CONTROL header; it is recommended that such refreshing of advertisements be done at a randomly-distributed interval of less than one-half of the advertisement expiration time, so as to provide the opportunity for recovery from lost advertisements before the advertisement expires, and to distribute over time the advertisement refreshment of multiple devices on the network in order to avoid spikes in network traffic
  230. (defvar *root-uuid* "93cb34ca-4bf2-4bc4-ad2b-5a7d1e423e53")
  231. (defvar *max-age* 1800)
  232. (defvar *os-version* "Chad partners")
  233. (defvar *product-version* #.(format nil "Chad Music/~A" (asdf:component-version
  234. (asdf:find-system :dlna-chad-music))))
  235. (defparameter +notification-subtype-alive+ "ssdp:alive")
  236. (defparameter +notification-subtype-byebye+ "ssdp:byebye")
  237. (defparameter +notify-cs+ "NOTIFY * HTTP/1.1~C~C~
  238. HOST: ~A:~A~C~C~
  239. CACHE-CONTROL: max-age=~A~C~C~
  240. LOCATION: ~A~C~C~
  241. NT: ~A~C~C~
  242. NTS: ~A~C~C~
  243. SERVER: ~A UPnP/1.0 ~A~C~C~
  244. USN: uuid:~A~@[::~A~]~C~C~
  245. ~C~C")
  246. (defparameter +upnp-root+ "upnp:rootdevice")
  247. (defparameter +urn-media-server+ "urn:schemas-upnp-org:device:MediaServer:1")
  248. (defun get-notify-message (location nst uuid &optional notify-type)
  249. (format nil +notify-cs+
  250. #\return #\linefeed
  251. +ssdp-multicast-group+ +ssdp-port+ #\return #\linefeed
  252. *max-age* #\return #\linefeed
  253. location #\return #\linefeed
  254. (or notify-type (format nil "uuid:~A" uuid)) #\return #\linefeed
  255. nst #\return #\linefeed
  256. *os-version* *product-version* #\return #\linefeed
  257. uuid notify-type #\return #\linefeed
  258. #\return #\linefeed))
  259. (defun get-location (local-addr)
  260. (format nil "http://~A:~A" local-addr *port*))
  261. (defun notify (fd local-addr)
  262. (let ((location (get-location local-addr)))
  263. (dolist (nt (list +upnp-root+ nil +urn-media-server+))
  264. (send-to fd +ssdp-multicast-group+ +ssdp-port+
  265. (get-notify-message location +notification-subtype-alive+ *root-uuid* nt)))))
  266. (defun get-local-addrs ()
  267. (cffi:with-foreign-object (ifa '(:pointer (:pointer (:struct wsock::ifaddrs))))
  268. (when (= (wsock::getifaddrs ifa) -1)
  269. (error 'os-error
  270. :description "Error calling getifaddrs"
  271. :code (wsys:errno)))
  272. (let ((head (cffi:mem-ref ifa '(:pointer (:struct wsock::ifaddrs)))))
  273. (unwind-protect
  274. (loop for ifaddr = head then (cffi:foreign-slot-value ifaddr '(:struct wsock::ifaddrs) 'wsock::next)
  275. until (cffi:null-pointer-p ifaddr)
  276. for addr = (cffi:foreign-slot-value ifaddr '(:struct wsock::ifaddrs) 'wsock::addr)
  277. unless (cffi:null-pointer-p addr)
  278. when (= (cffi:foreign-slot-value addr '(:struct wsock::sockaddr) 'wsock::family)
  279. wsock:+af-inet+)
  280. collect (cons (cffi:foreign-slot-value ifaddr '(:struct wsock::ifaddrs) 'wsock::name)
  281. (wsock:inet-ntoa (cffi:foreign-slot-value addr '(:struct wsock::sockaddr-in) 'wsock::addr))))
  282. (wsock::freeifaddrs head)))))
  283. (defun make-notify-sockets (&optional addrs)
  284. (loop for local-addr in (etypecase addrs
  285. (null (mapcar 'cdr (get-local-addrs)))
  286. (string (list addrs))
  287. (list addrs))
  288. for fd = (udp-socket local-addr nil)
  289. do (set-sock-int-opt fd wsock:+ipproto-ip+ wsock::+ip-multicast-ttl+ *notify-ip-ttl*)
  290. collect (list fd local-addr)))
  291. (defvar *ssdp-app* (lambda (env) (declare (ignore env))))
  292. (defun ssdp-read-cb (fd buffer length &rest socket-info)
  293. (declare (ignorable fd))
  294. (let* ((ssdp (fast-http:make-http-request))
  295. (parser (fast-http:make-parser
  296. ssdp
  297. :finish-callback
  298. (lambda ()
  299. (let ((env (make-ssdp-env ssdp socket-info)))
  300. (handle-ssdp-response fd env (funcall *ssdp-app* env)))))))
  301. (funcall parser buffer :start 0 :end length)))
  302. (defun make-ssdp-env (ssdp socket-info)
  303. (let* ((headers (fast-http:http-headers ssdp))
  304. (host (gethash "host" headers))
  305. (uri (fast-http:http-resource ssdp)))
  306. (declare (type simple-string uri))
  307. (multiple-value-bind (scheme userinfo hostname port path query fragment)
  308. (quri:parse-uri uri)
  309. (declare (ignore scheme userinfo hostname port fragment))
  310. (multiple-value-bind (server-name server-port)
  311. (if (stringp host)
  312. (woo::parse-host-header host)
  313. (values nil nil))
  314. (nconc
  315. (list :request-method (fast-http:http-method ssdp)
  316. :server-name server-name
  317. :server-port (or server-port 1900)
  318. :server-protocol (woo::http-version-keyword (woo::http-major-version ssdp)
  319. (woo::http-minor-version ssdp))
  320. :path-info (and path (quri:url-decode path :lenient t))
  321. :query-string query
  322. :url-scheme "httpu"
  323. :remote-addr (getf socket-info :src-addr)
  324. :remote-port (getf socket-info :src-port)
  325. :request-uri uri
  326. :content-length (let ((content-length (gethash "content-length" headers)))
  327. (etypecase content-length
  328. (string (parse-integer content-length :junk-allowed t))
  329. (integer content-length)
  330. (null nil)))
  331. :content-type (gethash "content-type" headers)
  332. :headers headers)
  333. socket-info)))))
  334. (defun handle-ssdp-response (fd env response)
  335. (when response
  336. (etypecase response
  337. (function (funcall response (lambda (res) (handle-ssdp-response fd env res))))
  338. (string (send-to fd (getf env :src-addr) (getf env :src-port) response)))))
  339. (defparameter +search-result-cs+ "HTTP/1.1 200 OK~C~C~
  340. CACHE-CONTROL: max-age=~A~C~C~
  341. DATE: ~A~C~C~
  342. EXT: ~C~C~
  343. LOCATION: ~A~C~C~
  344. SERVER: ~A UPnP/1.0 ~A~C~C~
  345. ST: ~A~C~C~
  346. USN: uuid:~A~@[::~A~]~C~C~
  347. ~C~C")
  348. (defun get-search-result-message (date location uuid &optional search-type)
  349. (format nil +search-result-cs+
  350. #\return #\linefeed
  351. *max-age* #\return #\linefeed
  352. date #\return #\linefeed
  353. #\return #\linefeed
  354. location #\return #\linefeed
  355. *os-version* *product-version* #\return #\linefeed
  356. (or search-type (format nil "uuid:~A" uuid)) #\return #\linefeed
  357. uuid search-type #\return #\linefeed
  358. #\return #\linefeed))
  359. (defun search-result (local-addr st)
  360. (get-search-result-message
  361. (woo.response::current-rfc-1123-timestamp)
  362. (get-location local-addr)
  363. *root-uuid* st))
  364. (defparameter +ssdp-all+ "ssdp:all")
  365. (defparameter +upnp-media-server+ "urn:schemas-upnp-org:device:MediaServer:1")
  366. (defparameter +valid-sts+ (list +ssdp-all+ +upnp-root+ +upnp-media-server+))
  367. (defun valid-st (st)
  368. (member st +valid-sts+ :test 'string-equal))
  369. (defparameter +timeout-thunks+ (make-hash-table))
  370. (define-c-callback with-timeout-cb :void ((evloop :pointer) (timer :pointer) (events :int))
  371. (declare (ignore evloop events))
  372. (let ((addr (cffi:pointer-address timer)))
  373. (funcall (gethash addr +timeout-thunks+))
  374. (remhash addr +timeout-thunks+)
  375. (cffi:foreign-free timer)))
  376. (defmacro with-timeout (timeout &body body)
  377. (with-gensyms (timer)
  378. `(let ((,timer (cffi:foreign-alloc '(:struct lev:ev-timer))))
  379. (setf (gethash (cffi:pointer-address ,timer) +timeout-thunks+) (lambda () ,@body))
  380. (lev:ev-timer-init ,timer 'with-timeout-cb (coerce ,timeout 'double-float) 0.0d0)
  381. (lev:ev-timer-start *evloop* ,timer))))
  382. (defparameter +retry-thunks+ (make-hash-table))
  383. (define-c-callback with-retry-cb :void ((evloop :pointer) (timer :pointer) (events :int))
  384. (declare (ignore evloop events))
  385. (let ((addr (cffi:pointer-address timer)))
  386. (funcall (gethash addr +retry-thunks+))))
  387. (defmacro with-retry ((retry thunk) &body body)
  388. (with-gensyms (timer)
  389. `(let ((,timer (cffi:foreign-alloc '(:struct lev:ev-timer))))
  390. (setf (gethash (cffi:pointer-address ,timer) +retry-thunks+) ,thunk)
  391. (lev:ev-timer-init ,timer 'with-retry-cb 0.0d0 (coerce ,retry 'double-float))
  392. (lev:ev-timer-again *evloop* ,timer)
  393. ,@body)))
  394. (defparameter +man-ssdp-discover+ "\"ssdp:discover\"")
  395. (defun ssdp-app (env)
  396. (let* ((headers (getf env :headers))
  397. (local-addr (getf env :local-addr))
  398. (mx (gethash "mx" headers))
  399. (st (gethash "st" headers)))
  400. (when (and (equal (getf env :request-method) :m-search)
  401. (string-equal (gethash "man" headers) +man-ssdp-discover+)
  402. (typep mx 'integer)
  403. (valid-st st))
  404. (lambda (handle-response)
  405. (with-timeout (random (coerce (min mx 120) 'float))
  406. (funcall handle-response (search-result local-addr st)))))))
  407. (defun start-ssdp-server (ssdp-app &key address (port 1900) fd (mcast (list +ssdp-multicast-group+)))
  408. (let ((*ssdp-app* ssdp-app))
  409. (with-udp-buffers
  410. (with-event-loop ()
  411. (let ((notify-sockets (make-notify-sockets)))
  412. (with-retry (5 #'(lambda () (loop for args in notify-sockets do (apply 'notify args))))
  413. (setf *ssdp-listener*
  414. (udp-server address port
  415. #'ssdp-read-cb
  416. :fd fd
  417. :sockopt wsock:+SO-REUSEADDR+
  418. :multicast mcast))))))))
  419. (defun 200-xml (data)
  420. (declare (optimize (speed 3) (safety 0))
  421. (type string data))
  422. `(200 (:content-type "text/xml; charset=\"utf-8\""
  423. "EXT" "")
  424. ,(trivial-utf-8:string-to-utf-8-bytes data)))
  425. (defparameter +root-desc+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  426. <root configId=\"926238\" xmlns=\"urn:schemas-upnp-org:device-1-0\" xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">
  427. <specVersion>
  428. <major>1</major>
  429. <minor>1</minor>
  430. </specVersion>
  431. <device>
  432. <deviceType>urn:schemas-upnp-org:device:MediaServer:1</deviceType>
  433. <friendlyName>Chad-Music</friendlyName>
  434. <manufacturer>Microsoft Corporation</manufacturer>
  435. <manufacturerURL>http://kodi.tv/</manufacturerURL>
  436. <modelDescription>Chad music</modelDescription>
  437. <modelName>chad-dlna</modelName>
  438. <modelNumber>18.2 Git:18.2-Leia</modelNumber>
  439. <modelURL>http://kodi.tv/</modelURL>
  440. <UDN>uuid:93cb34ca-4bf2-4bc4-ad2b-5a7d1e423e53</UDN>
  441. <presentationURL>http://192.168.1.50/</presentationURL>
  442. <dlna:X_DLNADOC xmlns:dlna=\"urn:schemas-dlna-org:device-1-0\">DMS-1.50</dlna:X_DLNADOC>
  443. <iconList>
  444. <icon>
  445. <mimetype>image/png</mimetype>
  446. <width>256</width>
  447. <height>256</height>
  448. <depth>8</depth>
  449. <url>/logo-256.png</url>
  450. </icon>
  451. </iconList>
  452. <serviceList>
  453. <service>
  454. <serviceType>urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1</serviceType>
  455. <serviceId>urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar</serviceId>
  456. <SCPDURL>/X_MS_MediaReceiverRegistrar/scpd.xml</SCPDURL>
  457. <controlURL>/X_MS_MediaReceiverRegistrar/control.xml</controlURL>
  458. <eventSubURL>/X_MS_MediaReceiverRegistrar/event.xml</eventSubURL>
  459. </service>
  460. <service>
  461. <serviceType>urn:schemas-upnp-org:service:ContentDirectory:1</serviceType>
  462. <serviceId>urn:upnp-org:serviceId:ContentDirectory</serviceId>
  463. <SCPDURL>/ContentDirectory/scpd.xml</SCPDURL>
  464. <controlURL>/ContentDirectory/control.xml</controlURL>
  465. <eventSubURL>/ContentDirectory/event.xml</eventSubURL>
  466. </service>
  467. <service>
  468. <serviceType>urn:schemas-upnp-org:service:ConnectionManager:1</serviceType>
  469. <serviceId>urn:upnp-org:serviceId:ConnectionManager</serviceId>
  470. <SCPDURL>/ConnectionManager/scpd.xml</SCPDURL>
  471. <controlURL>/ConnectionManager/control.xml</controlURL>
  472. <eventSubURL>/ConnectionManager/event.xml</eventSubURL>
  473. </service>
  474. </serviceList>
  475. </device>
  476. </root>
  477. ")
  478. (defun root-desc (params)
  479. (declare (ignore params))
  480. (200-xml +root-desc+))
  481. (defparameter +rec-reg-scpd+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  482. <scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">
  483. <specVersion>
  484. <major>1</major>
  485. <minor>0</minor>
  486. </specVersion>
  487. <actionList>
  488. <action>
  489. <name>IsAuthorized</name>
  490. <argumentList>
  491. <argument>
  492. <name>DeviceID</name>
  493. <direction>in</direction>
  494. <relatedStateVariable>A_ARG_TYPE_DeviceID</relatedStateVariable>
  495. </argument>
  496. <argument>
  497. <name>Result</name>
  498. <direction>out</direction>
  499. <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
  500. </argument>
  501. </argumentList>
  502. </action>
  503. <action>
  504. <name>RegisterDevice</name>
  505. <argumentList>
  506. <argument>
  507. <name>RegistrationReqMsg</name>
  508. <direction>in</direction>
  509. <relatedStateVariable>A_ARG_TYPE_RegistrationReqMsg</relatedStateVariable>
  510. </argument>
  511. <argument>
  512. <name>RegistrationRespMsg</name>
  513. <direction>out</direction>
  514. <relatedStateVariable>A_ARG_TYPE_RegistrationRespMsg</relatedStateVariable>
  515. </argument>
  516. </argumentList>
  517. </action>
  518. <action>
  519. <name>IsValidated</name>
  520. <argumentList>
  521. <argument>
  522. <name>DeviceID</name>
  523. <direction>in</direction>
  524. <relatedStateVariable>A_ARG_TYPE_DeviceID</relatedStateVariable>
  525. </argument>
  526. <argument>
  527. <name>Result</name>
  528. <direction>out</direction>
  529. <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
  530. </argument>
  531. </argumentList>
  532. </action>
  533. </actionList>
  534. <serviceStateTable>
  535. <stateVariable sendEvents=\"no\">
  536. <name>A_ARG_TYPE_DeviceID</name>
  537. <dataType>string</dataType>
  538. </stateVariable>
  539. <stateVariable sendEvents=\"no\">
  540. <name>A_ARG_TYPE_Result</name>
  541. <dataType>int</dataType>
  542. </stateVariable>
  543. <stateVariable sendEvents=\"no\">
  544. <name>A_ARG_TYPE_RegistrationReqMsg</name>
  545. <dataType>bin.base64</dataType>
  546. </stateVariable>
  547. <stateVariable sendEvents=\"no\">
  548. <name>A_ARG_TYPE_RegistrationRespMsg</name>
  549. <dataType>bin.base64</dataType>
  550. </stateVariable>
  551. <stateVariable sendEvents=\"yes\">
  552. <name>AuthorizationGrantedUpdateID</name>
  553. <dataType>ui4</dataType>
  554. </stateVariable>
  555. <stateVariable sendEvents=\"yes\">
  556. <name>AuthorizationDeniedUpdateID</name>
  557. <dataType>ui4</dataType>
  558. </stateVariable>
  559. <stateVariable sendEvents=\"yes\">
  560. <name>ValidationSucceededUpdateID</name>
  561. <dataType>ui4</dataType>
  562. </stateVariable>
  563. <stateVariable sendEvents=\"yes\">
  564. <name>ValidationRevokedUpdateID</name>
  565. <dataType>ui4</dataType>
  566. </stateVariable>
  567. </serviceStateTable>
  568. ")
  569. (defparameter +rec-reg-control+ "")
  570. (defparameter +rec-reg-event+ "")
  571. (defparameter +cont-dir-scpd+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  572. <scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">
  573. <specVersion>
  574. <major>1</major>
  575. <minor>0</minor>
  576. </specVersion>
  577. <actionList>
  578. <action>
  579. <name>Browse</name>
  580. <argumentList>
  581. <argument>
  582. <name>ObjectID</name>
  583. <direction>in</direction>
  584. <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
  585. </argument>
  586. <argument>
  587. <name>BrowseFlag</name>
  588. <direction>in</direction>
  589. <relatedStateVariable>A_ARG_TYPE_BrowseFlag</relatedStateVariable>
  590. </argument>
  591. <argument>
  592. <name>Filter</name>
  593. <direction>in</direction>
  594. <relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable>
  595. </argument>
  596. <argument>
  597. <name>StartingIndex</name>
  598. <direction>in</direction>
  599. <relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable>
  600. </argument>
  601. <argument>
  602. <name>RequestedCount</name>
  603. <direction>in</direction>
  604. <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
  605. </argument>
  606. <argument>
  607. <name>SortCriteria</name>
  608. <direction>in</direction>
  609. <relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable>
  610. </argument>
  611. <argument>
  612. <name>Result</name>
  613. <direction>out</direction>
  614. <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
  615. </argument>
  616. <argument>
  617. <name>NumberReturned</name>
  618. <direction>out</direction>
  619. <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
  620. </argument>
  621. <argument>
  622. <name>TotalMatches</name>
  623. <direction>out</direction>
  624. <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
  625. </argument>
  626. <argument>
  627. <name>UpdateID</name>
  628. <direction>out</direction>
  629. <relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable>
  630. </argument>
  631. </argumentList>
  632. </action>
  633. <action>
  634. <name>GetSortCapabilities</name>
  635. <argumentList>
  636. <argument>
  637. <name>SortCaps</name>
  638. <direction>out</direction>
  639. <relatedStateVariable>SortCapabilities</relatedStateVariable>
  640. </argument>
  641. </argumentList>
  642. </action>
  643. <action>
  644. <name>GetSystemUpdateID</name>
  645. <argumentList>
  646. <argument>
  647. <name>Id</name>
  648. <direction>out</direction>
  649. <relatedStateVariable>SystemUpdateID</relatedStateVariable>
  650. </argument>
  651. </argumentList>
  652. </action>
  653. <action>
  654. <name>GetSearchCapabilities</name>
  655. <argumentList>
  656. <argument>
  657. <name>SearchCaps</name>
  658. <direction>out</direction>
  659. <relatedStateVariable>SearchCapabilities</relatedStateVariable>
  660. </argument>
  661. </argumentList>
  662. </action>
  663. <action>
  664. <name>Search</name>
  665. <argumentList>
  666. <argument>
  667. <name>ContainerID</name>
  668. <direction>in</direction>
  669. <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
  670. </argument>
  671. <argument>
  672. <name>SearchCriteria</name>
  673. <direction>in</direction>
  674. <relatedStateVariable>A_ARG_TYPE_SearchCriteria</relatedStateVariable>
  675. </argument>
  676. <argument>
  677. <name>Filter</name>
  678. <direction>in</direction>
  679. <relatedStateVariable>A_ARG_TYPE_Filter</relatedStateVariable>
  680. </argument>
  681. <argument>
  682. <name>StartingIndex</name>
  683. <direction>in</direction>
  684. <relatedStateVariable>A_ARG_TYPE_Index</relatedStateVariable>
  685. </argument>
  686. <argument>
  687. <name>RequestedCount</name>
  688. <direction>in</direction>
  689. <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
  690. </argument>
  691. <argument>
  692. <name>SortCriteria</name>
  693. <direction>in</direction>
  694. <relatedStateVariable>A_ARG_TYPE_SortCriteria</relatedStateVariable>
  695. </argument>
  696. <argument>
  697. <name>Result</name>
  698. <direction>out</direction>
  699. <relatedStateVariable>A_ARG_TYPE_Result</relatedStateVariable>
  700. </argument>
  701. <argument>
  702. <name>NumberReturned</name>
  703. <direction>out</direction>
  704. <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
  705. </argument>
  706. <argument>
  707. <name>TotalMatches</name>
  708. <direction>out</direction>
  709. <relatedStateVariable>A_ARG_TYPE_Count</relatedStateVariable>
  710. </argument>
  711. <argument>
  712. <name>UpdateID</name>
  713. <direction>out</direction>
  714. <relatedStateVariable>A_ARG_TYPE_UpdateID</relatedStateVariable>
  715. </argument>
  716. </argumentList>
  717. </action>
  718. <action>
  719. <name>UpdateObject</name>
  720. <argumentList>
  721. <argument>
  722. <name>ObjectID</name>
  723. <direction>in</direction>
  724. <relatedStateVariable>A_ARG_TYPE_ObjectID</relatedStateVariable>
  725. </argument>
  726. <argument>
  727. <name>CurrentTagValue</name>
  728. <direction>in</direction>
  729. <relatedStateVariable>A_ARG_TYPE_TagValueList</relatedStateVariable>
  730. </argument>
  731. <argument>
  732. <name>NewTagValue</name>
  733. <direction>in</direction>
  734. <relatedStateVariable>A_ARG_TYPE_TagValueList</relatedStateVariable>
  735. </argument>
  736. </argumentList>
  737. </action>
  738. </actionList>
  739. <serviceStateTable>
  740. <stateVariable sendEvents=\"no\">
  741. <name>A_ARG_TYPE_BrowseFlag</name>
  742. <dataType>string</dataType>
  743. <allowedValueList>
  744. <allowedValue>BrowseMetadata</allowedValue>
  745. <allowedValue>BrowseDirectChildren</allowedValue>
  746. </allowedValueList>
  747. </stateVariable>
  748. <stateVariable sendEvents=\"yes\">
  749. <name>ContainerUpdateIDs</name>
  750. <dataType>string</dataType>
  751. </stateVariable>
  752. <stateVariable sendEvents=\"yes\">
  753. <name>SystemUpdateID</name>
  754. <dataType>ui4</dataType>
  755. </stateVariable>
  756. <stateVariable sendEvents=\"no\">
  757. <name>A_ARG_TYPE_Count</name>
  758. <dataType>ui4</dataType>
  759. </stateVariable>
  760. <stateVariable sendEvents=\"no\">
  761. <name>A_ARG_TYPE_SortCriteria</name>
  762. <dataType>string</dataType>
  763. </stateVariable>
  764. <stateVariable sendEvents=\"no\">
  765. <name>A_ARG_TYPE_SearchCriteria</name>
  766. <dataType>string</dataType>
  767. </stateVariable>
  768. <stateVariable sendEvents=\"no\">
  769. <name>SortCapabilities</name>
  770. <dataType>string</dataType>
  771. </stateVariable>
  772. <stateVariable sendEvents=\"no\">
  773. <name>A_ARG_TYPE_Index</name>
  774. <dataType>ui4</dataType>
  775. </stateVariable>
  776. <stateVariable sendEvents=\"no\">
  777. <name>A_ARG_TYPE_ObjectID</name>
  778. <dataType>string</dataType>
  779. </stateVariable>
  780. <stateVariable sendEvents=\"no\">
  781. <name>A_ARG_TYPE_UpdateID</name>
  782. <dataType>ui4</dataType>
  783. </stateVariable>
  784. <stateVariable sendEvents=\"no\">
  785. <name>A_ARG_TYPE_Result</name>
  786. <dataType>string</dataType>
  787. </stateVariable>
  788. <stateVariable sendEvents=\"no\">
  789. <name>SearchCapabilities</name>
  790. <dataType>string</dataType>
  791. </stateVariable>
  792. <stateVariable sendEvents=\"no\">
  793. <name>A_ARG_TYPE_Filter</name>
  794. <dataType>string</dataType>
  795. </stateVariable>
  796. <stateVariable sendEvents=\"no\">
  797. <name>A_ARG_TYPE_TagValueList</name>
  798. <dataType>string</dataType>
  799. </stateVariable>
  800. </serviceStateTable>
  801. </scpd>")
  802. (defparameter +conn-man-scpd+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  803. <scpd xmlns=\"urn:schemas-upnp-org:service-1-0\">
  804. <specVersion>
  805. <major>1</major>
  806. <minor>0</minor>
  807. </specVersion>
  808. <actionList>
  809. <action>
  810. <name>GetCurrentConnectionInfo</name>
  811. <argumentList>
  812. <argument>
  813. <name>ConnectionID</name>
  814. <direction>in</direction>
  815. <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
  816. </argument>
  817. <argument>
  818. <name>RcsID</name>
  819. <direction>out</direction>
  820. <relatedStateVariable>A_ARG_TYPE_RcsID</relatedStateVariable>
  821. </argument>
  822. <argument>
  823. <name>AVTransportID</name>
  824. <direction>out</direction>
  825. <relatedStateVariable>A_ARG_TYPE_AVTransportID</relatedStateVariable>
  826. </argument>
  827. <argument>
  828. <name>ProtocolInfo</name>
  829. <direction>out</direction>
  830. <relatedStateVariable>A_ARG_TYPE_ProtocolInfo</relatedStateVariable>
  831. </argument>
  832. <argument>
  833. <name>PeerConnectionManager</name>
  834. <direction>out</direction>
  835. <relatedStateVariable>A_ARG_TYPE_ConnectionManager</relatedStateVariable>
  836. </argument>
  837. <argument>
  838. <name>PeerConnectionID</name>
  839. <direction>out</direction>
  840. <relatedStateVariable>A_ARG_TYPE_ConnectionID</relatedStateVariable>
  841. </argument>
  842. <argument>
  843. <name>Direction</name>
  844. <direction>out</direction>
  845. <relatedStateVariable>A_ARG_TYPE_Direction</relatedStateVariable>
  846. </argument>
  847. <argument>
  848. <name>Status</name>
  849. <direction>out</direction>
  850. <relatedStateVariable>A_ARG_TYPE_ConnectionStatus</relatedStateVariable>
  851. </argument>
  852. </argumentList>
  853. </action>
  854. <action>
  855. <name>GetProtocolInfo</name>
  856. <argumentList>
  857. <argument>
  858. <name>Source</name>
  859. <direction>out</direction>
  860. <relatedStateVariable>SourceProtocolInfo</relatedStateVariable>
  861. </argument>
  862. <argument>
  863. <name>Sink</name>
  864. <direction>out</direction>
  865. <relatedStateVariable>SinkProtocolInfo</relatedStateVariable>
  866. </argument>
  867. </argumentList>
  868. </action>
  869. <action>
  870. <name>GetCurrentConnectionIDs</name>
  871. <argumentList>
  872. <argument>
  873. <name>ConnectionIDs</name>
  874. <direction>out</direction>
  875. <relatedStateVariable>CurrentConnectionIDs</relatedStateVariable>
  876. </argument>
  877. </argumentList>
  878. </action>
  879. </actionList>
  880. <serviceStateTable>
  881. <stateVariable sendEvents=\"no\">
  882. <name>A_ARG_TYPE_ProtocolInfo</name>
  883. <dataType>string</dataType>
  884. </stateVariable>
  885. <stateVariable sendEvents=\"no\">
  886. <name>A_ARG_TYPE_ConnectionStatus</name>
  887. <dataType>string</dataType>
  888. <allowedValueList>
  889. <allowedValue>OK</allowedValue>
  890. <allowedValue>ContentFormatMismatch</allowedValue>
  891. <allowedValue>InsufficientBandwidth</allowedValue>
  892. <allowedValue>UnreliableChannel</allowedValue>
  893. <allowedValue>Unknown</allowedValue>
  894. </allowedValueList>
  895. </stateVariable>
  896. <stateVariable sendEvents=\"no\">
  897. <name>A_ARG_TYPE_AVTransportID</name>
  898. <dataType>i4</dataType>
  899. </stateVariable>
  900. <stateVariable sendEvents=\"no\">
  901. <name>A_ARG_TYPE_RcsID</name>
  902. <dataType>i4</dataType>
  903. </stateVariable>
  904. <stateVariable sendEvents=\"no\">
  905. <name>A_ARG_TYPE_ConnectionID</name>
  906. <dataType>i4</dataType>
  907. </stateVariable>
  908. <stateVariable sendEvents=\"no\">
  909. <name>A_ARG_TYPE_ConnectionManager</name>
  910. <dataType>string</dataType>
  911. </stateVariable>
  912. <stateVariable sendEvents=\"yes\">
  913. <name>SourceProtocolInfo</name>
  914. <dataType>string</dataType>
  915. </stateVariable>
  916. <stateVariable sendEvents=\"yes\">
  917. <name>SinkProtocolInfo</name>
  918. <dataType>string</dataType>
  919. </stateVariable>
  920. <stateVariable sendEvents=\"no\">
  921. <name>A_ARG_TYPE_Direction</name>
  922. <dataType>string</dataType>
  923. <allowedValueList>
  924. <allowedValue>Input</allowedValue>
  925. <allowedValue>Output</allowedValue>
  926. </allowedValueList>
  927. </stateVariable>
  928. <stateVariable sendEvents=\"yes\">
  929. <name>CurrentConnectionIDs</name>
  930. <dataType>string</dataType>
  931. </stateVariable>
  932. </serviceStateTable>
  933. </scpd>")
  934. (defmacro static-xml (content)
  935. ``(200 (:content-type "text/xml; charset=\"utf-8\"") ,,(trivial-utf-8:string-to-utf-8-bytes (eval content))))
  936. (defun catch-all (params)
  937. (declare (ignore params))
  938. (print myway:*env*)
  939. '(404 nil nil))
  940. (defparameter +400+ '(400 nil #.(trivial-utf-8:string-to-utf-8-bytes "400")))
  941. (defparameter +404+ '(404 nil #.(trivial-utf-8:string-to-utf-8-bytes "404")))
  942. (defparameter +200+ '(200 nil #.(trivial-utf-8:string-to-utf-8-bytes "OK")))
  943. (defparameter *mapper* (myway:make-mapper))
  944. ;;(myway:connect *mapper* "/:entity/:id" 'post-entity :method :post)
  945. ;;(myway:connect *mapper* "/:entity/:id" 'get-entity)
  946. ;;(myway:connect *mapper* "/users/:id/visits" 'user-visits)
  947. ;;(myway:connect *mapper* "/locations/:id/avg" 'location-avg-mark)
  948. (myway:connect *mapper* "/logo-256.png" `(200 (:content-type "image/png") ,(pathname "logo-256.png")))
  949. (myway:connect *mapper* "/X_MS_MediaReceiverRegistrar/scpd.xml" (static-xml +rec-reg-scpd+))
  950. (myway:connect *mapper* "/X_MS_MediaReceiverRegistrar/control.xml" (static-xml +rec-reg-control+))
  951. (myway:connect *mapper* "/X_MS_MediaReceiverRegistrar/event.xml" +200+ :method :subscribe)
  952. (myway:connect *mapper* "/ContentDirectory/scpd.xml" (static-xml +cont-dir-scpd+))
  953. (myway:connect *mapper* "/ContentDirectory/control.xml" (static-xml +cont-dir-control+))
  954. (myway:connect *mapper* "/ContentDirectory/event.xml" +200+ :method :subscribe)
  955. (myway:connect *mapper* "/ConnectionManager/scpd.xml" (static-xml +conn-man-scpd+))
  956. (myway:connect *mapper* "/ConnectionManager/control.xml" (static-xml +conn-man-scpd+) :method :post)
  957. (myway:connect *mapper* "/ConnectionManager/event.xml" +200+ :method :subscribe)
  958. (myway:connect *mapper* "/" (static-xml +root-desc+))
  959. (myway:connect *mapper* "*" 'catch-all :method :post)
  960. (myway:connect *mapper* "*" 'catch-all :method :subscribe)
  961. (myway:connect *mapper* "*" 'catch-all)
  962. (myway:connect *mapper* "*" (lambda (p) (declare (ignore p)) +404+))
  963. (defun start (&key addrs)
  964. (let ((*ssdp-app* 'ssdp-app)
  965. (notify-sockets (make-notify-sockets addrs)))
  966. (with-udp-buffers
  967. (labels ((init-ssdp ()
  968. (with-retry (60 #'(lambda () (loop for args in notify-sockets do (apply 'notify args))))
  969. (setf *ssdp-listener*
  970. (udp-server nil +ssdp-port+
  971. #'ssdp-read-cb
  972. :sockopt wsock:+SO-REUSEADDR+
  973. :multicast (list +ssdp-multicast-group+))))))
  974. (woo:run (myway:to-app *mapper*) :port *port* :address nil :init-fn #'init-ssdp)))))