require 'simplecov' SimpleCov.start do add_filter "/spec/" end $:.unshift '../lib' require 'rupnp' require 'em-spec/rspec' require 'webmock/rspec' RUPNP.log_level = :failure WebMock.disable_net_connect!(allow_localhost: true) class FakeMulticast < RUPNP::SSDP::MulticastConnection attr_reader :handshake_response, :packets def onclose(&blk); @onclose = blk; end def onmessage(&blk); @onmessage = blk; end def initialize @packets = [] end def receive_data(data) @onmessage.call(data) if defined? @onmessage @packets << data end def unbind @onclose.call if defined? @onclose end end def generate_search_responder(uuid, port) responder = EM.open_datagram_socket(RUPNP::MULTICAST_IP, RUPNP::DISCOVERY_PORT, FakeMulticast) responder.onmessage do |data| data =~ /ST: (.*)\r\n/ target = $1 response =< 1, :version_minor => 1, :device_type => :base, }.merge(options) desc=< #{opt[:version_major]} #{opt[:version_minor]} urn:schemas-upnp-org:device:#{opt[:device_type].capitalize}:1-0 Friendly name RUPNP Model name uuid:#{uuid} EOD if opt[:device_type] != :base desc << < usrn:schemas-upnp-org:service:ContentDirectory:1 urn:upnp-org:serviceId:ContentDirectory /cd/description.xml /cd/control EOD end desc << " \n\n" end def generate_scpd(options={}) opt = { :version_major => 1, :version_minor => 1, :nb_state_var => 1, :define_action => false, :send_event => false }.merge(options) scpd=< #{opt[:version_major]} #{opt[:version_minor]} EOD if opt[:define_action] scpd << < testAction var1 in X_variableName1 var2 out X_variableName2 EOAL end scpd << ' ' opt[:nb_state_var].times do |i| scpd << < X_variableName#{i+1} ui4 #{i} 0 #{64*(i+1) - 1} EOSV end scpd << < X_variableStr string none none another value EOSV2 scpd << " \n\n" end def action_response(hash) r = < EOD hash.each { |k, v| r << " <#{k}>#{v}\n"} r << < EOD end def event_body < 12 EOD end def send_notify_request(req, options={}) delete = options.delete(:delete) headers = { 'HOST' => "127.0.0.1:1234", 'USER-AGENT' => RUPNP::USER_AGENT, 'CONTENT-TYPE' => 'text/xml; charset="utf-8"', 'NT' => 'upnp:event', 'NTS' => 'upnp:propchange', 'SID' => "uuid:#{UUID.generate}", 'SEQ' => 0 }.merge(options) headers.delete delete if delete req.setup_request(:notify, :head => headers, :body => event_body) end NOTIFY_REGEX = { :common => [ /^NOTIFY \* HTTP\/1.1\r\n/, /HOST: 239\.255\.255\.250:1900\r\n/, /NT: [0-9A-Za-z:-]+\r\n/, /USN: uuid:(.*)\r\n/, /BOOTID.UPNP.ORG: \d+\r\n/, /CONFIGID.UPNP.ORG: \d+\r\n/, ].freeze, :alive => [ /CACHE-CONTROL:\s+max-age\s+=\s+\d+\r\n/, /LOCATION: http:\/\/(.*)\r\n/, /NTS: ssdp:alive\r\n/, /SERVER: (.*)\r\n/, ].freeze, :byebye => [ /NTS: ssdp:byebye\r\n/, ].freeze, :update => [ /LOCATION: http:\/\/(.*)\r\n/, /NTS: ssdp:update\r\n/, /NEXTBOOTID.UPNP.ORG: \d+\r\n/, ].freeze } RSpec::Matchers.define :be_a_notify_packet do |type| match do |packet| success = NOTIFY_REGEX[:common].all? { |r| packet.match(r) } success &&= NOTIFY_REGEX[type].all? { |r| packet.match(r) } success && packet[-4..-1] == "\r\n\r\n" end end SEARCH_REGEX = [/^M-SEARCH \* HTTP\/1\.1\r\n/, /HOST: 239\.255\.255\.250:1900\r\n/, /MAN:\s+"ssdp:discover"\r\n/, /MX:\s+\d+\r\n/, /ST:\s+(ssdp:all|upnp:rootdevice|uuid:.+|urn:[\w:-]+)\r\n/, ].freeze RSpec::Matchers.define :be_a_msearch_packet do match do |packet| success = SEARCH_REGEX.all? { |r| packet.match(r) } success && packet[-4..-1] == "\r\n\r\n" end end