(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)))))