(in-package :cl-user)
(defpackage dlna-chad-music
(:use :cl)
(:import-from :woo.ev.event-loop
:check-event-loop-running
:with-event-loop
:callbacks
:*evloop*
:*input-buffer*)
(:import-from :alexandria
:when-let
:with-gensyms)
(:import-from :woo.ev.util
:define-c-callback
:io-fd))
(in-package :dlna-chad-music)
(defparameter +sockaddr-in-size+ (cffi:foreign-type-size '(:struct wsock::sockaddr-in)))
(defun set-sock-int-opt (fd level optname &optional (value 1))
(cffi:with-foreign-object (optval :int)
(setf (cffi:mem-aref optval :int) value)
(when (= (wsock:setsockopt fd level optname optval (cffi:foreign-type-size :int)) -1)
(error 'os-error
:description "Cannot set socket option"
:code (wsys:errno)))))
(defun udp-socket (address port &key sockopt)
(cffi:with-foreign-object (ai '(:pointer (:struct wsock:addrinfo)))
(cffi:with-foreign-object (hints '(:struct wsock:addrinfo))
(wsys:bzero hints (cffi:foreign-type-size '(:struct wsock:addrinfo)))
(cffi:with-foreign-slots ((wsock::family wsock::socktype wsock::flags) hints (:struct wsock:addrinfo))
(setf wsock::family (if (and (stringp address)
(quri.domain:ipv6-addr-p address))
wsock:+AF-INET6+
wsock:+AF-INET+)
wsock::socktype wsock:+SOCK-DGRAM+
wsock::flags wsock:+AI-PASSIVE+))
(let ((err (wsock:getaddrinfo (or address (cffi:null-pointer))
(if port (write-to-string port) (cffi:null-pointer))
hints ai)))
(unless (= err 0)
(error 'os-error
:description "getaddrinfo() failed"
:code err))))
(let ((ai (cffi:mem-ref ai :pointer)))
(cffi:with-foreign-slots ((wsock::family
wsock::socktype
wsock::protocol
wsock::addr
wsock::addrlen)
ai
(:struct wsock:addrinfo))
(let ((fd (wsock:socket wsock::family wsock::socktype wsock::protocol)))
(when (= fd -1)
(error 'os-error
:description (format nil "Cannot create udp socket (family=~S / socktype=~S / protocol=~S)"
wsock::family
wsock::socktype
wsock::protocol)
:code (wsys:errno)))
(when sockopt (set-sock-int-opt fd wsock:+SOL-SOCKET+ sockopt))
(when (= (wsock:bind fd wsock::addr wsock::addrlen) -1)
(error 'os-error
:description (format nil "Cannot bind fd to the address ~S" address)
:code (wsys:errno)))
(wsock:freeaddrinfo ai)
fd)))))
(defun join-multicast-group (fd address)
(cffi:with-foreign-object (mreq '(:struct wsock:ip-mreq))
(cffi:with-foreign-slots ((wsock::multiaddr wsock::interface) mreq (:struct wsock:ip-mreq))
(setf wsock::multiaddr (wsock:inet-addr address)
wsock::interface wsock:+inaddr-any+)
(when (= (wsock:setsockopt fd wsock:+ipproto-ip+
wsock:+ip-add-membership+ mreq
(cffi:foreign-type-size '(:struct wsock:ip-mreq))) -1)
(error 'os-error
:description (format nil "Cannot join fd to multicast group ~A" address)
:code (wsys:errno))))))
(defvar *udp-name-len* 128)
(defvar *udp-buffer-len* (* 1024 64))
(defvar *udp-control-len* 1024)
(defparameter *udp-message* nil)
(defparameter *udp-name* nil)
(defparameter *udp-iov* nil)
(defparameter *udp-control* nil)
(defparameter *udp-buffer* nil)
(defparameter *udp-sockstring* nil)
(defmacro with-udp-buffers (&body body)
`(let ((*udp-name* (static-vectors:make-static-vector *udp-name-len*))
(*udp-buffer* (static-vectors:make-static-vector *udp-buffer-len*))
(*udp-control* (static-vectors:make-static-vector *udp-control-len*))
(*udp-sockstring* (cffi:foreign-alloc :char :count wsock:+inet6-addrstrlen+)))
(cffi:with-foreign-objects
((*udp-message* '(:struct wsock:msghdr))
(*udp-iov* '(:struct wsock:iovec)))
(cffi:with-foreign-slots ((wsock::name wsock::iov wsock::iovlen wsock::control wsock::controllen)
*udp-message* (:struct wsock:msghdr))
(cffi:with-foreign-slots ((wsock::base wsock::len) *udp-iov* (:struct wsock:iovec))
(setf wsock::base (static-vectors:static-vector-pointer *udp-buffer*)
wsock::len (length *udp-buffer*)
wsock::name (static-vectors:static-vector-pointer *udp-name*)
;; namelen and controllen have to be set on all recvmsg
wsock::iov *udp-iov*
wsock::iovlen 1
wsock::control (static-vectors:static-vector-pointer *udp-control*))
(unwind-protect (progn ,@body)
(static-vectors:free-static-vector *udp-name*)
(static-vectors:free-static-vector *udp-buffer*)
(static-vectors:free-static-vector *udp-control*)
(cffi:foreign-free *udp-sockstring*)))))))
(defun get-name-port (sockaddr)
(let ((family (cffi:foreign-slot-value sockaddr '(:struct wsock:sockaddr-storage) 'wsock::family)))
(declare (type fixnum family))
(cond
((= family wsock:+AF-INET6+)
(wsock:inet-ntop family (cffi:foreign-slot-pointer sockaddr '(:struct wsock:sockaddr-in6) 'wsock::addr)
*udp-sockstring* wsock:+inet6-addrstrlen+)
(values
(cffi:foreign-string-to-lisp *udp-sockstring*)
(swap-bytes:ntohs (cffi:foreign-slot-value sockaddr '(:struct wsock:sockaddr-in6) 'wsock::port))))
((= family wsock:+AF-INET+)
(wsock:inet-ntop family (cffi:foreign-slot-pointer sockaddr '(:struct wsock:sockaddr-in) 'wsock::addr)
*udp-sockstring* wsock:+inet6-addrstrlen+)
(values
(cffi:foreign-string-to-lisp *udp-sockstring*)
(swap-bytes:ntohs (cffi:foreign-slot-value sockaddr '(:struct wsock:sockaddr-in) 'wsock::port))))
(:otherwise (values nil nil)))))
(defmacro with-sockaddr-in ((var addr port) &body body)
`(cffi:with-foreign-object (,var '(:struct wsock::sockaddr-in))
(cffi:with-foreign-slots ((wsock::family wsock::addr wsock::port) ,var (:struct wsock::sockaddr-in))
(setf wsock::family wsock:+af-inet+
wsock::addr (wsock:inet-addr ,addr)
wsock::port (swap-bytes:htons ,port))
,@body)))
(defun send-to (fd addr port data)
(with-sockaddr-in (dest addr port)
(let ((message (trivial-utf-8:string-to-utf-8-bytes data)))
(cffi:with-pointer-to-vector-data (buffer data)
(wsock:sendto fd buffer (length message) 0 dest +sockaddr-in-size+)))))
(defun align-h (size)
(declare (optimize (speed 3) (safety 0)))
(declare (type (unsigned-byte 32) size))
(let ((r (1- (the (integer 4 32) (cffi:foreign-type-size 'wsock::size-t)))))
(logand (+ size r) (lognot r))))
(defun align-d (size) (align-h size))
(defun cmsg-next (msg cmsg)
(cffi:with-foreign-slots ((wsock::control wsock::controllen) msg (:struct wsock::msghdr))
(declare (type (unsigned-byte 32) wsock::controllen))
(if (cffi:null-pointer-p cmsg)
(if (>= wsock::controllen (the (unsigned-byte 32) (cffi:foreign-type-size '(:struct wsock:cmsghdr))))
wsock::control (cffi:null-pointer))
(let ((next (cffi:inc-pointer cmsg (align-h (the (unsigned-byte 32) (cffi:foreign-slot-value cmsg '(:struct wsock:cmsghdr) 'wsock::len))))))
(if (> (cffi:pointer-address (cffi:inc-pointer next (align-d (cffi:foreign-type-size '(:struct wsock:cmsghdr)))))
(cffi:pointer-address (cffi:inc-pointer wsock::control wsock::controllen)))
(cffi:null-pointer)
next)))))
(defun cmsg-data (cmsg)
(declare (optimize (speed 3) (safety 0)))
(cffi:inc-pointer cmsg (align-d (cffi:foreign-type-size '(:struct wsock:cmsghdr)))))
(defun parse-cmsg (level type data len)
(cond
((and (= level wsock:+ipproto-ip+)
(= type wsock:+ip-pktinfo+))
(cffi:with-foreign-slots ((wsock::if-index wsock::spec-dst wsock::addr) data (:struct wsock::pktinfo))
(list :iface-idx wsock::if-index :local-addr (wsock:inet-ntoa wsock::spec-dst) :dst-addr (wsock:inet-ntoa wsock::addr))))
((and (= level wsock:+ipproto-ip+)
(= type wsock::+ip-recvorigdstaddr+))
(cffi:with-foreign-slots ((wsock::family wsock::port wsock::addr) data (:struct wsock::sockaddr-in))
(list :dst-family wsock::family :dst-port (swap-bytes:ntohs wsock::port) :dst-addr (wsock:inet-ntoa wsock::addr))))
(t (list (intern (format nil "LEVEL-~a-TYPE-~a" level type) :keyword)
(cffi:foreign-array-to-lisp data `(:array :unsigned-char ,len))))))
(defun get-cmsgs (msg)
(loop for cmsg = (cmsg-next msg (cffi:null-pointer)) then (cmsg-next msg cmsg)
until (cffi:null-pointer-p cmsg)
append (cffi:with-foreign-slots ((wsock::level wsock::type wsock::len) cmsg (:struct wsock:cmsghdr))
(parse-cmsg wsock::level wsock::type (cmsg-data cmsg)
(- wsock::len (align-d (cffi:foreign-type-size '(:struct wsock:cmsghdr))))))))
(defun get-name-aux (message)
(multiple-value-bind (addr port)
(get-name-port (cffi:foreign-slot-value message '(:struct wsock:msghdr) 'wsock::name))
(append (list :src-addr addr :src-port port) (get-cmsgs message))))
(defun dump-message (message len)
(cffi:with-foreign-slots ((wsock::name wsock::namelen wsock::iov wsock::control wsock::controllen)
message (:struct wsock:msghdr))
(cffi:with-foreign-slots ((wsock::base wsock::len) wsock::iov (:struct wsock:iovec))
(format t "~A ~A~%~A ~A~%~A ~A~%"
wsock::namelen (cffi:foreign-array-to-lisp wsock::name `(:array :unsigned-char ,wsock::namelen))
len (cffi:foreign-string-to-lisp wsock::base :count len)
wsock::controllen (cffi:foreign-array-to-lisp wsock::control `(:array :unsigned-char ,wsock::controllen))))))
(define-c-callback udp-read-cb :void ((evloop :pointer) (watcher :pointer) (events :int))
(declare (ignore evloop events))
(let* ((fd (io-fd watcher))
(read-cb (callbacks fd)))
(setf (cffi:foreign-slot-value *udp-message* '(:struct wsock:msghdr) 'wsock::namelen) *udp-name-len*
(cffi:foreign-slot-value *udp-message* '(:struct wsock:msghdr) 'wsock::controllen) *udp-control-len*)
(let ((len (wsock:recvmsg fd *udp-message* 0)))
(declare (type fixnum len))
;; (dump-message *udp-message* len)
(if (= len -1)
(let ((errno (wsys:errno)))
(cond
;; Just to nothing
((or (= errno wsys:EWOULDBLOCK)
(= errno wsys:EINTR)
(= errno wsys:EAGAIN)))
(t
(vom:error "Unexpected error (Code: ~D)" errno))))
(when read-cb
(apply (the function read-cb) fd *udp-buffer* len (get-name-aux *udp-message*)))))))
(defun udp-server (address port read-cb &key fd (sockopt wsock:+SO-REUSEADDR+) multicast)
(check-event-loop-running)
(let ((fd (or fd (udp-socket address port :sockopt sockopt)))
(listener (cffi:foreign-alloc '(:struct lev:ev-io))))
(when (= (wsys:set-fd-nonblock fd t) -1)
(error 'os-error
:description "Cannot set fd nonblock"
:code (wsys:errno)))
#+linux (set-sock-int-opt fd wsock:+ipproto-ip+ wsock:+ip-pktinfo+)
(loop for address in multicast
do (join-multicast-group fd address))
(setf (callbacks fd) read-cb)
(lev:ev-io-init listener 'udp-read-cb fd lev:+EV-READ+)
(lev:ev-io-start *evloop* listener)
listener))
(defparameter +ssdp-multicast-group+ "239.255.255.250")
(defparameter +ssdp-port+ 1900)
(defvar *notify-ip-ttl* 4)
(defvar *ssdp-listener* nil)
(defvar *port* 50000)
;; 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.
;; 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
(defvar *root-uuid* "93cb34ca-4bf2-4bc4-ad2b-5a7d1e423e53")
(defvar *max-age* 1800)
(defvar *os-version* "Chad partners")
(defvar *product-version* #.(format nil "Chad Music/~A" (asdf:component-version
(asdf:find-system :dlna-chad-music))))
(defparameter +notification-subtype-alive+ "ssdp:alive")
(defparameter +notification-subtype-byebye+ "ssdp:byebye")
(defparameter +notify-cs+ "NOTIFY * HTTP/1.1~C~C~
HOST: ~A:~A~C~C~
CACHE-CONTROL: max-age=~A~C~C~
LOCATION: ~A~C~C~
NT: ~A~C~C~
NTS: ~A~C~C~
SERVER: ~A UPnP/1.0 ~A~C~C~
USN: uuid:~A~@[::~A~]~C~C~
~C~C")
(defparameter +upnp-root+ "upnp:rootdevice")
(defparameter +urn-media-server+ "urn:schemas-upnp-org:device:MediaServer:1")
(defun get-notify-message (location nst uuid &optional notify-type)
(format nil +notify-cs+
#\return #\linefeed
+ssdp-multicast-group+ +ssdp-port+ #\return #\linefeed
*max-age* #\return #\linefeed
location #\return #\linefeed
(or notify-type (format nil "uuid:~A" uuid)) #\return #\linefeed
nst #\return #\linefeed
*os-version* *product-version* #\return #\linefeed
uuid notify-type #\return #\linefeed
#\return #\linefeed))
(defun get-location (local-addr)
(format nil "http://~A:~A" local-addr *port*))
(defun notify (fd local-addr)
(let ((location (get-location local-addr)))
(dolist (nt (list +upnp-root+ nil +urn-media-server+))
(send-to fd +ssdp-multicast-group+ +ssdp-port+
(get-notify-message location +notification-subtype-alive+ *root-uuid* nt)))))
(defun get-local-addrs ()
(cffi:with-foreign-object (ifa '(:pointer (:pointer (:struct wsock::ifaddrs))))
(when (= (wsock::getifaddrs ifa) -1)
(error 'os-error
:description "Error calling getifaddrs"
:code (wsys:errno)))
(let ((head (cffi:mem-ref ifa '(:pointer (:struct wsock::ifaddrs)))))
(unwind-protect
(loop for ifaddr = head then (cffi:foreign-slot-value ifaddr '(:struct wsock::ifaddrs) 'wsock::next)
until (cffi:null-pointer-p ifaddr)
for addr = (cffi:foreign-slot-value ifaddr '(:struct wsock::ifaddrs) 'wsock::addr)
unless (cffi:null-pointer-p addr)
when (= (cffi:foreign-slot-value addr '(:struct wsock::sockaddr) 'wsock::family)
wsock:+af-inet+)
collect (cons (cffi:foreign-slot-value ifaddr '(:struct wsock::ifaddrs) 'wsock::name)
(wsock:inet-ntoa (cffi:foreign-slot-value addr '(:struct wsock::sockaddr-in) 'wsock::addr))))
(wsock::freeifaddrs head)))))
(defun make-notify-sockets (&optional addrs)
(loop for local-addr in (etypecase addrs
(null (mapcar 'cdr (get-local-addrs)))
(string (list addrs))
(list addrs))
for fd = (udp-socket local-addr nil)
do (set-sock-int-opt fd wsock:+ipproto-ip+ wsock::+ip-multicast-ttl+ *notify-ip-ttl*)
collect (list fd local-addr)))
(defvar *ssdp-app* (lambda (env) (declare (ignore env))))
(defun ssdp-read-cb (fd buffer length &rest socket-info)
(declare (ignorable fd))
(let* ((ssdp (fast-http:make-http-request))
(parser (fast-http:make-parser
ssdp
:finish-callback
(lambda ()
(let ((env (make-ssdp-env ssdp socket-info)))
(handle-ssdp-response fd env (funcall *ssdp-app* env)))))))
(funcall parser buffer :start 0 :end length)))
(defun make-ssdp-env (ssdp socket-info)
(let* ((headers (fast-http:http-headers ssdp))
(host (gethash "host" headers))
(uri (fast-http:http-resource ssdp)))
(declare (type simple-string uri))
(multiple-value-bind (scheme userinfo hostname port path query fragment)
(quri:parse-uri uri)
(declare (ignore scheme userinfo hostname port fragment))
(multiple-value-bind (server-name server-port)
(if (stringp host)
(woo::parse-host-header host)
(values nil nil))
(nconc
(list :request-method (fast-http:http-method ssdp)
:server-name server-name
:server-port (or server-port 1900)
:server-protocol (woo::http-version-keyword (woo::http-major-version ssdp)
(woo::http-minor-version ssdp))
:path-info (and path (quri:url-decode path :lenient t))
:query-string query
:url-scheme "httpu"
:remote-addr (getf socket-info :src-addr)
:remote-port (getf socket-info :src-port)
:request-uri uri
:content-length (let ((content-length (gethash "content-length" headers)))
(etypecase content-length
(string (parse-integer content-length :junk-allowed t))
(integer content-length)
(null nil)))
:content-type (gethash "content-type" headers)
:headers headers)
socket-info)))))
(defun handle-ssdp-response (fd env response)
(when response
(etypecase response
(function (funcall response (lambda (res) (handle-ssdp-response fd env res))))
(string (send-to fd (getf env :src-addr) (getf env :src-port) response)))))
(defparameter +search-result-cs+ "HTTP/1.1 200 OK~C~C~
CACHE-CONTROL: max-age=~A~C~C~
DATE: ~A~C~C~
EXT: ~C~C~
LOCATION: ~A~C~C~
SERVER: ~A UPnP/1.0 ~A~C~C~
ST: ~A~C~C~
USN: uuid:~A~@[::~A~]~C~C~
~C~C")
(defun get-search-result-message (date location uuid &optional search-type)
(format nil +search-result-cs+
#\return #\linefeed
*max-age* #\return #\linefeed
date #\return #\linefeed
#\return #\linefeed
location #\return #\linefeed
*os-version* *product-version* #\return #\linefeed
(or search-type (format nil "uuid:~A" uuid)) #\return #\linefeed
uuid search-type #\return #\linefeed
#\return #\linefeed))
(defun search-result (local-addr st)
(get-search-result-message
(woo.response::current-rfc-1123-timestamp)
(get-location local-addr)
*root-uuid* st))
(defparameter +ssdp-all+ "ssdp:all")
(defparameter +upnp-media-server+ "urn:schemas-upnp-org:device:MediaServer:1")
(defparameter +valid-sts+ (list +ssdp-all+ +upnp-root+ +upnp-media-server+))
(defun valid-st (st)
(member st +valid-sts+ :test 'string-equal))
(defparameter +timeout-thunks+ (make-hash-table))
(define-c-callback with-timeout-cb :void ((evloop :pointer) (timer :pointer) (events :int))
(declare (ignore evloop events))
(let ((addr (cffi:pointer-address timer)))
(funcall (gethash addr +timeout-thunks+))
(remhash addr +timeout-thunks+)
(cffi:foreign-free timer)))
(defmacro with-timeout (timeout &body body)
(with-gensyms (timer)
`(let ((,timer (cffi:foreign-alloc '(:struct lev:ev-timer))))
(setf (gethash (cffi:pointer-address ,timer) +timeout-thunks+) (lambda () ,@body))
(lev:ev-timer-init ,timer 'with-timeout-cb (coerce ,timeout 'double-float) 0.0d0)
(lev:ev-timer-start *evloop* ,timer))))
(defparameter +retry-thunks+ (make-hash-table))
(define-c-callback with-retry-cb :void ((evloop :pointer) (timer :pointer) (events :int))
(declare (ignore evloop events))
(let ((addr (cffi:pointer-address timer)))
(funcall (gethash addr +retry-thunks+))))
(defmacro with-retry ((retry thunk) &body body)
(with-gensyms (timer)
`(let ((,timer (cffi:foreign-alloc '(:struct lev:ev-timer))))
(setf (gethash (cffi:pointer-address ,timer) +retry-thunks+) ,thunk)
(lev:ev-timer-init ,timer 'with-retry-cb 0.0d0 (coerce ,retry 'double-float))
(lev:ev-timer-again *evloop* ,timer)
,@body)))
(defparameter +man-ssdp-discover+ "\"ssdp:discover\"")
(defun ssdp-app (env)
(let* ((headers (getf env :headers))
(local-addr (getf env :local-addr))
(mx (gethash "mx" headers))
(st (gethash "st" headers)))
(when (and (equal (getf env :request-method) :m-search)
(string-equal (gethash "man" headers) +man-ssdp-discover+)
(typep mx 'integer)
(valid-st st))
(lambda (handle-response)
(with-timeout (random (coerce (min mx 120) 'float))
(funcall handle-response (search-result local-addr st)))))))
(defun start-ssdp-server (ssdp-app &key address (port 1900) fd (mcast (list +ssdp-multicast-group+)))
(let ((*ssdp-app* ssdp-app))
(with-udp-buffers
(with-event-loop ()
(let ((notify-sockets (make-notify-sockets)))
(with-retry (5 #'(lambda () (loop for args in notify-sockets do (apply 'notify args))))
(setf *ssdp-listener*
(udp-server address port
#'ssdp-read-cb
:fd fd
:sockopt wsock:+SO-REUSEADDR+
:multicast mcast))))))))
(defun 200-xml (data)
(declare (optimize (speed 3) (safety 0))
(type string data))
`(200 (:content-type "text/xml; charset=\"utf-8\""
"EXT" "")
,(trivial-utf-8:string-to-utf-8-bytes data)))
(defparameter +root-desc+ "
1
1
urn:schemas-upnp-org:device:MediaServer:1
Chad-Music
Microsoft Corporation
http://kodi.tv/
Chad music
chad-dlna
18.2 Git:18.2-Leia
http://kodi.tv/
uuid:93cb34ca-4bf2-4bc4-ad2b-5a7d1e423e53
http://192.168.1.50/
DMS-1.50
image/png
256
256
8
/logo-256.png
urn:microsoft.com:service:X_MS_MediaReceiverRegistrar:1
urn:microsoft.com:serviceId:X_MS_MediaReceiverRegistrar
/X_MS_MediaReceiverRegistrar/scpd.xml
/X_MS_MediaReceiverRegistrar/control.xml
/X_MS_MediaReceiverRegistrar/event.xml
urn:schemas-upnp-org:service:ContentDirectory:1
urn:upnp-org:serviceId:ContentDirectory
/ContentDirectory/scpd.xml
/ContentDirectory/control.xml
/ContentDirectory/event.xml
urn:schemas-upnp-org:service:ConnectionManager:1
urn:upnp-org:serviceId:ConnectionManager
/ConnectionManager/scpd.xml
/ConnectionManager/control.xml
/ConnectionManager/event.xml
")
(defun root-desc (params)
(declare (ignore params))
(200-xml +root-desc+))
(defparameter +rec-reg-scpd+ "
1
0
IsAuthorized
DeviceID
in
A_ARG_TYPE_DeviceID
Result
out
A_ARG_TYPE_Result
RegisterDevice
RegistrationReqMsg
in
A_ARG_TYPE_RegistrationReqMsg
RegistrationRespMsg
out
A_ARG_TYPE_RegistrationRespMsg
IsValidated
DeviceID
in
A_ARG_TYPE_DeviceID
Result
out
A_ARG_TYPE_Result
A_ARG_TYPE_DeviceID
string
A_ARG_TYPE_Result
int
A_ARG_TYPE_RegistrationReqMsg
bin.base64
A_ARG_TYPE_RegistrationRespMsg
bin.base64
AuthorizationGrantedUpdateID
ui4
AuthorizationDeniedUpdateID
ui4
ValidationSucceededUpdateID
ui4
ValidationRevokedUpdateID
ui4
")
(defparameter +rec-reg-control+ "")
(defparameter +rec-reg-event+ "")
(defparameter +cont-dir-scpd+ "
1
0
Browse
ObjectID
in
A_ARG_TYPE_ObjectID
BrowseFlag
in
A_ARG_TYPE_BrowseFlag
Filter
in
A_ARG_TYPE_Filter
StartingIndex
in
A_ARG_TYPE_Index
RequestedCount
in
A_ARG_TYPE_Count
SortCriteria
in
A_ARG_TYPE_SortCriteria
Result
out
A_ARG_TYPE_Result
NumberReturned
out
A_ARG_TYPE_Count
TotalMatches
out
A_ARG_TYPE_Count
UpdateID
out
A_ARG_TYPE_UpdateID
GetSortCapabilities
SortCaps
out
SortCapabilities
GetSystemUpdateID
Id
out
SystemUpdateID
GetSearchCapabilities
SearchCaps
out
SearchCapabilities
Search
ContainerID
in
A_ARG_TYPE_ObjectID
SearchCriteria
in
A_ARG_TYPE_SearchCriteria
Filter
in
A_ARG_TYPE_Filter
StartingIndex
in
A_ARG_TYPE_Index
RequestedCount
in
A_ARG_TYPE_Count
SortCriteria
in
A_ARG_TYPE_SortCriteria
Result
out
A_ARG_TYPE_Result
NumberReturned
out
A_ARG_TYPE_Count
TotalMatches
out
A_ARG_TYPE_Count
UpdateID
out
A_ARG_TYPE_UpdateID
UpdateObject
ObjectID
in
A_ARG_TYPE_ObjectID
CurrentTagValue
in
A_ARG_TYPE_TagValueList
NewTagValue
in
A_ARG_TYPE_TagValueList
A_ARG_TYPE_BrowseFlag
string
BrowseMetadata
BrowseDirectChildren
ContainerUpdateIDs
string
SystemUpdateID
ui4
A_ARG_TYPE_Count
ui4
A_ARG_TYPE_SortCriteria
string
A_ARG_TYPE_SearchCriteria
string
SortCapabilities
string
A_ARG_TYPE_Index
ui4
A_ARG_TYPE_ObjectID
string
A_ARG_TYPE_UpdateID
ui4
A_ARG_TYPE_Result
string
SearchCapabilities
string
A_ARG_TYPE_Filter
string
A_ARG_TYPE_TagValueList
string
")
(defparameter +conn-man-scpd+ "
1
0
GetCurrentConnectionInfo
ConnectionID
in
A_ARG_TYPE_ConnectionID
RcsID
out
A_ARG_TYPE_RcsID
AVTransportID
out
A_ARG_TYPE_AVTransportID
ProtocolInfo
out
A_ARG_TYPE_ProtocolInfo
PeerConnectionManager
out
A_ARG_TYPE_ConnectionManager
PeerConnectionID
out
A_ARG_TYPE_ConnectionID
Direction
out
A_ARG_TYPE_Direction
Status
out
A_ARG_TYPE_ConnectionStatus
GetProtocolInfo
Source
out
SourceProtocolInfo
Sink
out
SinkProtocolInfo
GetCurrentConnectionIDs
ConnectionIDs
out
CurrentConnectionIDs
A_ARG_TYPE_ProtocolInfo
string
A_ARG_TYPE_ConnectionStatus
string
OK
ContentFormatMismatch
InsufficientBandwidth
UnreliableChannel
Unknown
A_ARG_TYPE_AVTransportID
i4
A_ARG_TYPE_RcsID
i4
A_ARG_TYPE_ConnectionID
i4
A_ARG_TYPE_ConnectionManager
string
SourceProtocolInfo
string
SinkProtocolInfo
string
A_ARG_TYPE_Direction
string
Input
Output
CurrentConnectionIDs
string
")
(defmacro static-xml (content)
``(200 (:content-type "text/xml; charset=\"utf-8\"") ,,(trivial-utf-8:string-to-utf-8-bytes (eval content))))
(defun catch-all (params)
(declare (ignore params))
(print myway:*env*)
'(404 nil nil))
(defparameter +400+ '(400 nil #.(trivial-utf-8:string-to-utf-8-bytes "400")))
(defparameter +404+ '(404 nil #.(trivial-utf-8:string-to-utf-8-bytes "404")))
(defparameter +200+ '(200 nil #.(trivial-utf-8:string-to-utf-8-bytes "OK")))
(defparameter *mapper* (myway:make-mapper))
;;(myway:connect *mapper* "/:entity/:id" 'post-entity :method :post)
;;(myway:connect *mapper* "/:entity/:id" 'get-entity)
;;(myway:connect *mapper* "/users/:id/visits" 'user-visits)
;;(myway:connect *mapper* "/locations/:id/avg" 'location-avg-mark)
(myway:connect *mapper* "/logo-256.png" `(200 (:content-type "image/png") ,(pathname "logo-256.png")))
(myway:connect *mapper* "/X_MS_MediaReceiverRegistrar/scpd.xml" (static-xml +rec-reg-scpd+))
(myway:connect *mapper* "/X_MS_MediaReceiverRegistrar/control.xml" (static-xml +rec-reg-control+))
(myway:connect *mapper* "/X_MS_MediaReceiverRegistrar/event.xml" +200+ :method :subscribe)
(myway:connect *mapper* "/ContentDirectory/scpd.xml" (static-xml +cont-dir-scpd+))
(myway:connect *mapper* "/ContentDirectory/control.xml" (static-xml +cont-dir-control+))
(myway:connect *mapper* "/ContentDirectory/event.xml" +200+ :method :subscribe)
(myway:connect *mapper* "/ConnectionManager/scpd.xml" (static-xml +conn-man-scpd+))
(myway:connect *mapper* "/ConnectionManager/control.xml" (static-xml +conn-man-scpd+) :method :post)
(myway:connect *mapper* "/ConnectionManager/event.xml" +200+ :method :subscribe)
(myway:connect *mapper* "/" (static-xml +root-desc+))
(myway:connect *mapper* "*" 'catch-all :method :post)
(myway:connect *mapper* "*" 'catch-all :method :subscribe)
(myway:connect *mapper* "*" 'catch-all)
(myway:connect *mapper* "*" (lambda (p) (declare (ignore p)) +404+))
(defun start (&key addrs)
(let ((*ssdp-app* 'ssdp-app)
(notify-sockets (make-notify-sockets addrs)))
(with-udp-buffers
(labels ((init-ssdp ()
(with-retry (60 #'(lambda () (loop for args in notify-sockets do (apply 'notify args))))
(setf *ssdp-listener*
(udp-server nil +ssdp-port+
#'ssdp-read-cb
:sockopt wsock:+SO-REUSEADDR+
:multicast (list +ssdp-multicast-group+))))))
(woo:run (myway:to-app *mapper*) :port *port* :address nil :init-fn #'init-ssdp)))))