diff --git a/hueplusplus/test/mocks/mock_HttpHandler.h b/hueplusplus/test/mocks/mock_HttpHandler.h new file mode 100755 index 0000000..70ac7bc --- /dev/null +++ b/hueplusplus/test/mocks/mock_HttpHandler.h @@ -0,0 +1,61 @@ +/** + \file mock_HttpHandler.h + Copyright Notice\n + Copyright (C) 2017 Jan Rogall - developer\n + Copyright (C) 2017 Moritz Wirger - developer\n + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +**/ + +#ifndef _MOCK_HTTPHANDLER_H +#define _MOCK_HTTPHANDLER_H + +#include +#include + +#include + +#include "../hueplusplus/include/HttpHandler.h" +#include "../hueplusplus/include/json/json.h" + +//! Mock Class +class MockHttpHandler : public IHttpHandler +{ +public: + + MOCK_CONST_METHOD3( send, std::string(const std::string &msg, const std::string &adr, int port) ); + + MOCK_CONST_METHOD3( sendGetHTTPBody, std::string(const std::string &msg, const std::string &adr, int port) ); + + MOCK_CONST_METHOD4( sendMulticast, std::vector(const std::string &msg, const std::string &adr, int port, int timeout) ); + + MOCK_CONST_METHOD6( sendHTTPRequest, std::string(std::string method, std::string uri, std::string content_type, std::string body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD5( GETString, std::string(std::string uri, std::string content_type, std::string body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD5( POSTString, std::string(std::string uri, std::string content_type, std::string body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD5( PUTString, std::string(std::string uri, std::string content_type, std::string body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD5( DELETEString, std::string(std::string uri, std::string content_type, std::string body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD4( GETJson, Json::Value(std::string uri, const Json::Value& body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD4( POSTJson, Json::Value(std::string uri, const Json::Value& body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD4( PUTJson, Json::Value(std::string uri, const Json::Value& body, const std::string &adr, int port) ); + + MOCK_CONST_METHOD4( DELETEJson, Json::Value(std::string uri, const Json::Value& body, const std::string &adr, int port) ); +}; + +#endif diff --git a/hueplusplus/test/test_Hue.cpp b/hueplusplus/test/test_Hue.cpp new file mode 100755 index 0000000..254b9db --- /dev/null +++ b/hueplusplus/test/test_Hue.cpp @@ -0,0 +1,395 @@ +#include +#include + +#include "../include/Hue.h" +#include "../include/json/json.h" +#include "mocks/mock_HttpHandler.h" +#include "testhelper.h" + +#include +#include +#include + + +class HueFinderTest : public ::testing::Test +{ +protected: + std::shared_ptr handler; +protected: + HueFinderTest() + { + handler = std::make_shared(); + + EXPECT_CALL(*handler, sendMulticast("M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: \"ssdp:discover\"\r\nMX: 5\r\nST: ssdp:all\r\n\r\n", "239.255.255.250", 1900, 5)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly(::testing::Return(multicast_reply)); + + EXPECT_CALL(*handler, GETString("/description.xml", "application/xml", "", "192.168.2.1", 80)) + .Times(0); + + EXPECT_CALL(*handler, GETString("/description.xml", "application/xml", "", bridge_ip, 80)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly(::testing::Return(brige_xml)); + } + ~HueFinderTest(){}; +}; + +TEST_F(HueFinderTest, FindBridges) +{ + HueFinder finder(handler); + std::vector bridges = finder.FindBridges(); + + HueFinder::HueIdentification bridge_to_comp; + bridge_to_comp.ip = bridge_ip; + bridge_to_comp.mac = bridge_mac; + + EXPECT_EQ(bridges.size(), 1) << "HueFinder found more than one Bridge"; + EXPECT_EQ(bridges[0].ip, bridge_to_comp.ip) << "HueIdentification ip does not match"; + EXPECT_EQ(bridges[0].mac, bridge_to_comp.mac) << "HueIdentification mac does not match"; +} + +TEST_F(HueFinderTest, GetBridge) +{ + Json::Value request; + request["devicetype"] = "HuePlusPlus#User"; + + Json::Value user_ret_uns; + user_ret_uns = Json::Value(Json::arrayValue); + user_ret_uns[0] = Json::Value(Json::objectValue); + user_ret_uns[0]["error"] = Json::Value(Json::objectValue); + user_ret_uns[0]["error"]["type"] = 101; + user_ret_uns[0]["error"]["address"] = ""; + user_ret_uns[0]["error"]["description"] = "link button not pressed"; + + EXPECT_CALL(*handler, GETJson("/api", request, bridge_ip, 80)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly(::testing::Return(user_ret_uns)); + + HueFinder finder(handler); + std::vector bridges = finder.FindBridges(); + + ASSERT_THROW(finder.GetBridge(bridges[0]), std::runtime_error); + + Json::Value user_ret_suc; + user_ret_suc = Json::Value(Json::arrayValue); + user_ret_suc[0] = Json::Value(Json::objectValue); + user_ret_suc[0]["success"] = Json::Value(Json::objectValue); + user_ret_suc[0]["success"]["username"] = bridge_username; + + EXPECT_CALL(*handler, GETJson("/api", request, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(user_ret_suc)); + + finder = HueFinder(handler); + bridges = finder.FindBridges(); + + Hue test_bridge = finder.GetBridge(bridges[0]); + + EXPECT_EQ(test_bridge.getBridgeIP(), bridge_ip) << "Bridge IP not matching"; + EXPECT_EQ(test_bridge.getUsername(), bridge_username) << "Bridge username not matching"; + + +} + +TEST_F(HueFinderTest, AddUsername) +{ + HueFinder finder(handler); + std::vector bridges = finder.FindBridges(); + + finder.AddUsername(bridges[0].mac, bridge_username); + Hue test_bridge = finder.GetBridge(bridges[0]); + + EXPECT_EQ(test_bridge.getBridgeIP(), bridge_ip) << "Bridge IP not matching"; + EXPECT_EQ(test_bridge.getUsername(), bridge_username) << "Bridge username not matching"; +} + +TEST_F(HueFinderTest, GetAllUsernames) +{ + HueFinder finder(handler); + std::vector bridges = finder.FindBridges(); + + finder.AddUsername(bridges[0].mac, bridge_username); + + std::map users = finder.GetAllUsernames(); + EXPECT_EQ(users[bridge_mac], bridge_username) << "Username of MAC:" << bridge_mac << "not matching"; +} + +TEST(Hue, Constructor) +{ + std::shared_ptr handler = std::make_shared(); + Hue test_bridge(bridge_ip, bridge_username, handler); + + EXPECT_EQ(test_bridge.getBridgeIP(), bridge_ip) << "Bridge IP not matching"; + EXPECT_EQ(test_bridge.getUsername(), bridge_username) << "Bridge username not matching"; +} + +TEST(Hue, requestUsername) +{ + std::shared_ptr handler = std::make_shared(); + Json::Value request; + request["devicetype"] = "HuePlusPlus#User"; + + Json::Value user_ret_uns; + user_ret_uns = Json::Value(Json::arrayValue); + user_ret_uns[0] = Json::Value(Json::objectValue); + user_ret_uns[0]["error"] = Json::Value(Json::objectValue); + user_ret_uns[0]["error"]["type"] = 101; + user_ret_uns[0]["error"]["address"] = ""; + user_ret_uns[0]["error"]["description"] = "link button not pressed"; + + EXPECT_CALL(*handler, GETJson("/api", request, bridge_ip, 80)) + .Times(::testing::AtLeast(1)) + .WillRepeatedly(::testing::Return(user_ret_uns)); + + Hue test_bridge(bridge_ip, "", handler); + + test_bridge.requestUsername(test_bridge.getBridgeIP()); + EXPECT_EQ(test_bridge.getUsername(), "") << "Bridge username not matching"; + + Json::Value user_ret_suc; + user_ret_suc = Json::Value(Json::arrayValue); + user_ret_suc[0] = Json::Value(Json::objectValue); + user_ret_suc[0]["success"] = Json::Value(Json::objectValue); + user_ret_suc[0]["success"]["username"] = bridge_username; + EXPECT_CALL(*handler, GETJson("/api", request, bridge_ip, 80)) + .Times(1) + .WillRepeatedly(::testing::Return(user_ret_suc)); + + test_bridge = Hue(bridge_ip, "", handler); + + test_bridge.requestUsername(test_bridge.getBridgeIP()); + + EXPECT_EQ(test_bridge.getBridgeIP(), bridge_ip) << "Bridge IP not matching"; + EXPECT_EQ(test_bridge.getUsername(), bridge_username) << "Bridge username not matching"; +} + +TEST(Hue, setIP) +{ + std::shared_ptr handler = std::make_shared(); + Hue test_bridge(bridge_ip, "", handler); + EXPECT_EQ(test_bridge.getBridgeIP(), bridge_ip) << "Bridge IP not matching after initialization"; + test_bridge.setIP("192.168.2.112"); + EXPECT_EQ(test_bridge.getBridgeIP(), "192.168.2.112") << "Bridge IP not matching after setting it"; +} + +TEST(Hue, getLight) +{ + std::shared_ptr handler = std::make_shared(); + Json::Value empty_json_obj(Json::objectValue); + EXPECT_CALL(*handler, GETJson("/api/"+bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1); + + Hue test_bridge(bridge_ip, bridge_username, handler); + + // Test exception + ASSERT_THROW(test_bridge.getLight(1), std::runtime_error); + + Json::Value hue_bridge_state; + hue_bridge_state["lights"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["state"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["state"]["on"] = true; + hue_bridge_state["lights"]["1"]["state"]["bri"] = 254; + hue_bridge_state["lights"]["1"]["state"]["ct"] = 366; + hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; + hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; + hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; + hue_bridge_state["lights"]["1"]["swupdate"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; + hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = Json::nullValue; + hue_bridge_state["lights"]["1"]["type"] = "Color temperature light"; + hue_bridge_state["lights"]["1"]["name"] = "Hue ambiance lamp 1"; + hue_bridge_state["lights"]["1"]["modelid"] = "LTW001"; + hue_bridge_state["lights"]["1"]["manufacturername"] = "Philips"; + hue_bridge_state["lights"]["1"]["uniqueid"] = "00:00:00:00:00:00:00:00-00"; + hue_bridge_state["lights"]["1"]["swversion"] = "5.50.1.19085"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(3) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + + // Test when correct data is sent + HueLight test_light_1 = test_bridge.getLight(1); + EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); + + // Test again to check whether light is returned directly -> interesting for code coverage test + test_light_1 = test_bridge.getLight(1); + EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_light_1.getColorType(), ColorType::TEMPERATURE); + + + + // more coverage stuff + hue_bridge_state["lights"]["1"]["modelid"] = "LCT001"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + test_bridge = Hue(bridge_ip, bridge_username, handler); + + // Test when correct data is sent + test_light_1 = test_bridge.getLight(1); + EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_B); + + + hue_bridge_state["lights"]["1"]["modelid"] = "LCT010"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + test_bridge = Hue(bridge_ip, bridge_username, handler); + + // Test when correct data is sent + test_light_1 = test_bridge.getLight(1); + EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_C); + + + hue_bridge_state["lights"]["1"]["modelid"] = "LST001"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + test_bridge = Hue(bridge_ip, bridge_username, handler); + + // Test when correct data is sent + test_light_1 = test_bridge.getLight(1); + EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_light_1.getColorType(), ColorType::GAMUT_A); + + + hue_bridge_state["lights"]["1"]["modelid"] = "LWB004"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + test_bridge = Hue(bridge_ip, bridge_username, handler); + + // Test when correct data is sent + test_light_1 = test_bridge.getLight(1); + EXPECT_EQ(test_light_1.getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_light_1.getColorType(), ColorType::NONE); + + + hue_bridge_state["lights"]["1"]["modelid"] = "ABC000"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + test_bridge = Hue(bridge_ip, bridge_username, handler); + + ASSERT_THROW(test_bridge.getLight(1), std::runtime_error); + +} + +TEST(Hue, removeLight) +{ + std::shared_ptr handler = std::make_shared(); + Json::Value empty_json_obj(Json::objectValue); + Json::Value hue_bridge_state; + hue_bridge_state["lights"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["state"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["state"]["on"] = true; + hue_bridge_state["lights"]["1"]["state"]["bri"] = 254; + hue_bridge_state["lights"]["1"]["state"]["ct"] = 366; + hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; + hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; + hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; + hue_bridge_state["lights"]["1"]["swupdate"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; + hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = Json::nullValue; + hue_bridge_state["lights"]["1"]["type"] = "Color temperature light"; + hue_bridge_state["lights"]["1"]["name"] = "Hue ambiance lamp 1"; + hue_bridge_state["lights"]["1"]["modelid"] = "LTW001"; + hue_bridge_state["lights"]["1"]["manufacturername"] = "Philips"; + hue_bridge_state["lights"]["1"]["uniqueid"] = "00:00:00:00:00:00:00:00-00"; + hue_bridge_state["lights"]["1"]["swversion"] = "5.50.1.19085"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillOnce(::testing::Return(hue_bridge_state)); + + Hue test_bridge(bridge_ip, bridge_username, handler); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(1) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + + Json::Value return_answer; + return_answer = Json::Value(Json::arrayValue); + return_answer[0] = Json::Value(Json::objectValue); + return_answer[0]["success"] = "/lights/1 deleted"; + EXPECT_CALL(*handler, DELETEJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillOnce(::testing::Return(return_answer)); + + // Test when correct data is sent + HueLight test_light_1 = test_bridge.getLight(1); + + EXPECT_EQ(test_bridge.removeLight(1), true); + + EXPECT_EQ(test_bridge.removeLight(1), false); +} + +TEST(Hue, getAllLights) +{ + std::shared_ptr handler = std::make_shared(); + Json::Value empty_json_obj(Json::objectValue); + Json::Value hue_bridge_state; + hue_bridge_state["lights"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["state"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["state"]["on"] = true; + hue_bridge_state["lights"]["1"]["state"]["bri"] = 254; + hue_bridge_state["lights"]["1"]["state"]["ct"] = 366; + hue_bridge_state["lights"]["1"]["state"]["alert"] = "none"; + hue_bridge_state["lights"]["1"]["state"]["colormode"] = "ct"; + hue_bridge_state["lights"]["1"]["state"]["reachable"] = true; + hue_bridge_state["lights"]["1"]["swupdate"] = Json::Value(Json::objectValue); + hue_bridge_state["lights"]["1"]["swupdate"]["state"] = "noupdates"; + hue_bridge_state["lights"]["1"]["swupdate"]["lastinstall"] = Json::nullValue; + hue_bridge_state["lights"]["1"]["type"] = "Color temperature light"; + hue_bridge_state["lights"]["1"]["name"] = "Hue ambiance lamp 1"; + hue_bridge_state["lights"]["1"]["modelid"] = "LTW001"; + hue_bridge_state["lights"]["1"]["manufacturername"] = "Philips"; + hue_bridge_state["lights"]["1"]["uniqueid"] = "00:00:00:00:00:00:00:00-00"; + hue_bridge_state["lights"]["1"]["swversion"] = "5.50.1.19085"; + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username, empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillRepeatedly(::testing::Return(hue_bridge_state)); + + EXPECT_CALL(*handler, GETJson("/api/" + bridge_username + "/lights/1", empty_json_obj, bridge_ip, 80)) + .Times(2) + .WillRepeatedly(::testing::Return(hue_bridge_state["lights"]["1"])); + + Hue test_bridge(bridge_ip, bridge_username, handler); + + std::vector> test_lights = test_bridge.getAllLights(); + EXPECT_EQ(test_lights[0].get().getName(), "Hue ambiance lamp 1"); + EXPECT_EQ(test_lights[0].get().getColorType(), ColorType::TEMPERATURE); +} + +TEST(Hue, refreshState) +{ + std::shared_ptr handler = std::make_shared(); + Hue test_bridge(bridge_ip, "", handler); // NULL as username leads to segfault + + std::vector> test_lights = test_bridge.getAllLights(); + EXPECT_EQ(test_lights.size(), 0); +} diff --git a/hueplusplus/test/test_Main.cpp b/hueplusplus/test/test_Main.cpp new file mode 100755 index 0000000..d67f881 --- /dev/null +++ b/hueplusplus/test/test_Main.cpp @@ -0,0 +1,7 @@ +#include + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/hueplusplus/test/testhelper.h b/hueplusplus/test/testhelper.h new file mode 100755 index 0000000..ddb6e42 --- /dev/null +++ b/hueplusplus/test/testhelper.h @@ -0,0 +1,219 @@ + +const std::string bridge_ip = "192.168.2.116"; //!< IP-Address of the fake hue bridge in dotted decimal notation like "192.168.2.1" +const std::string bridge_username = "83b7780291a6ceffbe0bd049104df"; //!< Username that is ussed to access the fake hue bridge +const std::string bridge_id = "111111FFFE11E111"; +const std::string bridge_uuid = "1f111f11-da11-11e1-1b11-11111111e111"; +const std::string bridge_mac = "11111111e111"; + +const std::string brige_xml = "" + "" + "" + "1" + "0" + "" + "http://192.168.2.116:80/" + "" + "urn:schemas-upnp-org:device:Basic:1" + "Philips hue (192.168.2.116)" + "Royal Philips Electronics" + "http://www.philips.com" + "Philips hue Personal Wireless Lighting" + "Philips hue bridge 2015" + "BSB002" + "http://www.meethue.com" + "11111111e111" + "uuid:1f111f11-da11-11e1-1b11-11111111e111" + "index.html" + "" + "" + "image/png" + "48" + "48" + "24" + "hue_logo_0.png" + "" + "" + "" + ""; + +const std::vector multicast_reply = { + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::upnp:rootdevice", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::urn:schemas-upnp-org:device:InternetGatewayDevice:1", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: urn:schemas-upnp-org:service:Layer3Forwarding:1\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::urn:schemas-upnp-org:service:Layer3Forwarding:1", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: urn:schemas-upnp-org:device:WANDevice:1\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::urn:schemas-upnp-org:device:WANDevice:1", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: urn:schemas-upnp-org:device:WANConnectionDevice:1\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::urn:schemas-upnp-org:device:WANConnectionDevice:1", + + "HTTP/1.1 200 OK\r\n" + "CACHE-CONTROL: max-age=300\r\n" + "DATE: Wed, 21 Jan 1970 05:42:21 GMT\r\n" + "EXT:\r\n" + "LOCATION: http://192.168.2.1:1900/gatedesc.xml\r\n" + "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n" + "01-NLS: 000c0000-0dd0-00b0-0da0-00a000e000c0\r\n" + "SERVER: Linux/2.6.36, UPnP/1.0, Portable SDK for UPnP devices/1.6.19\r\n" + "X-User-Agent: redsonic\r\n" + "ST: urn:schemas-upnp-org:service:WANIPConnection:1\r\n" + "USN: uuid:0f0000b0-f0da-0ad0-00b0-0000000fdf00::urn:schemas-upnp-org:service:WANIPConnection:1", + + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "EXT:\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: http://192.168.2.116:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0\r\n" + "hue-bridgeid: 111111FFFE11E111\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:1f111f11-da11-11e1-1b11-11111111e111::upnp:rootdevice", + + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "EXT:\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: http://192.168.2.116:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0\r\n" + "hue-bridgeid: 111111FFFE11E111\r\n" + "ST: uuid:1f111f11-da11-11e1-1b11-11111111e111\r\n" + "USN: uuid:1f111f11-da11-11e1-1b11-11111111e111", + + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "EXT:\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: http://192.168.2.116:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0\r\n" + "hue-bridgeid: 111111FFFE11E111\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:1f111f11-da11-11e1-1b11-11111111e111", + + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "EXT:\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: http://192.168.2.116:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0\r\n" + "hue-bridgeid: 111111FFFE11E111\r\n" + "ST: upnp:rootdevice\r\n" + "USN: uuid:1f111f11-da11-11e1-1b11-11111111e111::upnp:rootdevice", + + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "EXT:\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: http://192.168.2.116:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0\r\n" + "hue-bridgeid: 111111FFFE11E111\r\n" + "ST: uuid:1f111f11-da11-11e1-1b11-11111111e111\r\n" + "USN: uuid:1f111f11-da11-11e1-1b11-11111111e111", + + "HTTP/1.1 200 OK\r\n" + "HOST: 239.255.255.250:1900\r\n" + "EXT:\r\n" + "CACHE-CONTROL: max-age=100\r\n" + "LOCATION: http://192.168.2.116:80/description.xml\r\n" + "SERVER: Linux/3.14.0 UPnP/1.0 IpBridge/1.21.0\r\n" + "hue-bridgeid: 111111FFFE11E111\r\n" + "ST: urn:schemas-upnp-org:device:basic:1\r\n" + "USN: uuid:1f111f11-da11-11e1-1b11-11111111e111" +};