Skip to content

Commit

Permalink
[hueemulation] seperate ipv4 / ipv6
Browse files Browse the repository at this point in the history
Resolves exception of combining ipv4 / ipv6 by getting to separate channels

Closes openhab#5589

Exception in thread "HueEmulation UPNP Server" java.lang.IllegalArgumentException: IPv6 socket cannot join IPv4 multicast group
        at sun.nio.ch.DatagramChannelImpl.innerJoin(DatagramChannelImpl.java:808)
        at sun.nio.ch.DatagramChannelImpl.join(DatagramChannelImpl.java:894)
        at org.openhab.io.hueemulation.internal.upnp.UpnpServer.accept(UpnpServer.java:439)
        at org.openhab.io.hueemulation.internal.upnp.UpnpServer.accept(UpnpServer.java:1)
        at org.openhab.io.hueemulation.internal.upnp.HueEmulationConfigWithRuntime.run(HueEmulationConfigWithRuntime.java:105)

Signed-off-by: Martin van Wingerden <martin@martinvw.nl>
  • Loading branch information
martinvw committed Jun 14, 2019
1 parent 1c5ce81 commit 97d8bc2
Show file tree
Hide file tree
Showing 13 changed files with 59 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
*/
public class JerseyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
public void onStartup(Set<Class<?>> c, ServletContext ctx) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,7 @@ public static class JerseyApplication extends Application {
public class LogAccessInterceptor implements ContainerResponseFilter {
@NonNullByDefault({})
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) {

if (!logger.isDebugEnabled()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public synchronized void dispose() {
}

@Override
public Duration call() throws Exception {
public Duration call() {
((TriggerHandlerCallback) callback).triggered(module, null);
config.repeat -= 1;
if (config.repeat == 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public Response getReducedConfigApi() {
@ApiOperation(value = "Return the full data store")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getAllApi(@Context UriInfo uri,
@PathParam("username") @ApiParam(value = "username") String username) throws IOException {
@PathParam("username") @ApiParam(value = "username") String username) {
if (!userManagement.authorizeUser(username)) {
return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
}
Expand All @@ -91,7 +91,7 @@ public Response getAllApi(@Context UriInfo uri,
@ApiOperation(value = "Return the configuration")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getFullConfigApi(@Context UriInfo uri,
@PathParam("username") @ApiParam(value = "username") String username) throws IOException {
@PathParam("username") @ApiParam(value = "username") String username) {
if (!userManagement.authorizeUser(username)) {
return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
}
Expand All @@ -104,7 +104,7 @@ public Response getFullConfigApi(@Context UriInfo uri,
@ApiOperation(value = "Return the reduced configuration")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response putFullConfigApi(@Context UriInfo uri,
@PathParam("username") @ApiParam(value = "username") String username, String body) throws IOException {
@PathParam("username") @ApiParam(value = "username") String username, String body) {
if (!userManagement.authorizeUser(username)) {
return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ protected static List<Action> createActions(String uid, List<HueCommand> hueActi
@ApiOperation(value = "Return all rules")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getRulesApi(@Context UriInfo uri,
@PathParam("username") @ApiParam(value = "username") String username) throws IOException {
@PathParam("username") @ApiParam(value = "username") String username) {
if (!userManagement.authorizeUser(username)) {
return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public void updated(Rule oldElement, Rule element) {
@ApiOperation(value = "Return all scenes")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getScenesApi(@Context UriInfo uri,
@PathParam("username") @ApiParam(value = "username") String username) throws IOException {
@PathParam("username") @ApiParam(value = "username") String username) {
if (!userManagement.authorizeUser(username)) {
return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ protected static Rule createRule(String uid, RuleBuilder builder, List<Action> o
@ApiOperation(value = "Return all schedules")
@ApiResponses(value = { @ApiResponse(code = 200, message = "OK") })
public Response getSchedulesApi(@Context UriInfo uri,
@PathParam("username") @ApiParam(value = "username") String username) throws IOException {
@PathParam("username") @ApiParam(value = "username") String username) {
if (!userManagement.authorizeUser(username)) {
return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,7 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.DatagramChannel;
Expand All @@ -37,7 +28,6 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.ForkJoinPool;
Expand Down Expand Up @@ -155,7 +145,7 @@ public UpnpServer(Executor executor) {
*/
@NonNullByDefault({})
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
if (xmlDocWithAddress == null || xmlDocWithAddress.isEmpty()) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
Expand Down Expand Up @@ -208,7 +198,7 @@ protected void activate() {
}

private void useAddressPort(HueEmulationConfigWithRuntime r) {
final String urlBase = "http://" + r.addressString + ":" + String.valueOf(r.port);
final String urlBase = "http://" + r.addressString + ":" + r.port;
this.baseurl = urlBase + DISCOVERY_FILE;

final String[] stVersions = { "upnp:rootdevice", "urn:schemas-upnp-org:device:basic:1",
Expand Down Expand Up @@ -271,7 +261,7 @@ private void useAddressPort(HueEmulationConfigWithRuntime r) {
ip = "[" + ip.split("%")[0] + "]";
}
try {
url = "http://" + ip + ":" + String.valueOf(config.port) + DISCOVERY_FILE;
url = "http://" + ip + ":" + config.port + DISCOVERY_FILE;
response = client.target(url).request().get();
boolean isOurs = response.readEntity(String.class).contains(cs.ds.config.bridgeid);
selfTests.add(new SelfTestReport(url, response.getStatus() == 200, isOurs));
Expand Down Expand Up @@ -308,7 +298,7 @@ private void useAddressPort(HueEmulationConfigWithRuntime r) {
r = new HueEmulationConfigWithRuntime(this, cs.getConfig(), cs.ds.config.ipaddress, MULTI_ADDR_IPV4,
MULTI_ADDR_IPV6);
} catch (UnknownHostException e) {
logger.warn("The picked default IP address is not valid: ", e.getMessage());
logger.warn("The picked default IP address is not valid: {}", e.getMessage());
throw new IllegalStateException(e);
}
return r;
Expand Down Expand Up @@ -363,8 +353,6 @@ public synchronized void handleEvent(@Nullable Event event) {

/**
* Stops the upnp server from running
*
* @throws InterruptedException
*/
@Deactivate
public void deactivate() {
Expand All @@ -375,7 +363,7 @@ public void deactivate() {
}
}

private void handleRead(SelectionKey key, Set<InetAddress> addresses) throws IOException {
private void handleRead(SelectionKey key) throws IOException {
logger.trace("upnp thread handle received message");
DatagramChannel channel = (DatagramChannel) key.channel();
ClientRecord clntRec = (ClientRecord) key.attachment();
Expand Down Expand Up @@ -424,22 +412,25 @@ public void accept(HueEmulationConfigWithRuntime threadContext) {
boolean hasIPv4 = false;
boolean hasIPv6 = false;

try (DatagramChannel channel = DatagramChannel.open(); Selector selector = Selector.open()) {
try (
DatagramChannel channelV4 = DatagramChannel.open(StandardProtocolFamily.INET);
DatagramChannel channelV6 = DatagramChannel.open(StandardProtocolFamily.INET6);
Selector selector = Selector.open()) {

threadContext.asyncIOselector = selector;

channel.setOption(StandardSocketOptions.SO_REUSEADDR, true)
.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, true).bind(new InetSocketAddress(UPNP_PORT));
bind(channelV4);
bind(channelV6);
for (InetAddress address : cs.getDiscoveryIps()) {
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(address);
if (networkInterface == null) {
continue;
}
if (address instanceof Inet4Address) {
channel.join(MULTI_ADDR_IPV4, networkInterface);
channelV4.join(MULTI_ADDR_IPV4, networkInterface);
hasIPv4 = true;
} else {
channel.join(MULTI_ADDR_IPV6, networkInterface);
channelV6.join(MULTI_ADDR_IPV6, networkInterface);
hasIPv6 = true;
}
}
Expand All @@ -450,16 +441,18 @@ public void accept(HueEmulationConfigWithRuntime threadContext) {
return;
}

channel.configureBlocking(false);

channel.register(selector, SelectionKey.OP_READ, new ClientRecord());

if (hasIPv4) {
channelV4.configureBlocking(false);
channelV4.register(selector, SelectionKey.OP_READ, new ClientRecord());
try (DatagramSocket sendSocket = new DatagramSocket(new InetSocketAddress(config.address, 0))) {
sendUPNPDatagrams(sendSocket, MULTI_ADDR_IPV4, UPNP_PORT);
}
}
if (hasIPv6) {
channelV6.configureBlocking(false);
channelV6.register(selector, SelectionKey.OP_READ, new ClientRecord());
try (DatagramSocket sendSocket = new DatagramSocket()) {
sendUPNPDatagrams(sendSocket, MULTI_ADDR_IPV6, UPNP_PORT);
}
Expand All @@ -475,7 +468,7 @@ public void accept(HueEmulationConfigWithRuntime threadContext) {
while (keyIter.hasNext()) {
SelectionKey key = keyIter.next();
if (key.isReadable()) {
handleRead(key, cs.getDiscoveryIps());
handleRead(key);
}
keyIter.remove();
}
Expand Down Expand Up @@ -504,6 +497,12 @@ public void accept(HueEmulationConfigWithRuntime threadContext) {
}
}

private void bind(DatagramChannel channel) throws IOException {
channel.setOption(StandardSocketOptions.SO_REUSEADDR, true)
.setOption(StandardSocketOptions.IP_MULTICAST_LOOP, true)
.bind(new InetSocketAddress(UPNP_PORT));
}

/**
* The upnp server performs some self-tests
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ protected LocalDateTime getNow() {
}

@Before
public void setUp() throws IOException {
public void setUp() {
ds = new HueDataStore();

ds.lights.put("1", new HueLightEntry(new SwitchItem("switch"), "switch", DeviceType.SwitchType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public void tearDown() {
}

@Test
public void addSwitchableByCategory() throws IOException {
public void addSwitchableByCategory() {
SwitchItem item = new SwitchItem("switch1");
item.setCategory("Light");
itemRegistry.add(item);
Expand All @@ -100,7 +100,7 @@ public void addSwitchableByCategory() throws IOException {
}

@Test
public void addSwitchableByTag() throws IOException {
public void addSwitchableByTag() {
SwitchItem item = new SwitchItem("switch1");
item.addTag("Switchable");
itemRegistry.add(item);
Expand All @@ -110,7 +110,7 @@ public void addSwitchableByTag() throws IOException {
}

@Test
public void ignoreByTag() throws IOException {
public void ignoreByTag() {
SwitchItem item = new SwitchItem("switch1");
item.addTags("Switchable", "internal"); // The ignore tag will win
itemRegistry.add(item);
Expand All @@ -119,7 +119,7 @@ public void ignoreByTag() throws IOException {
}

@Test
public void addGroupSwitchableByTag() throws IOException {
public void addGroupSwitchableByTag() {
GroupItem item = new GroupItem("group1", new SwitchItem("switch1"));
item.addTag("Switchable");
itemRegistry.add(item);
Expand All @@ -129,7 +129,7 @@ public void addGroupSwitchableByTag() throws IOException {
}

@Test
public void addGroupWithoutTypeByTag() throws IOException {
public void addGroupWithoutTypeByTag() {
GroupItem item = new GroupItem("group1", null);
item.addTag("Switchable");

Expand All @@ -142,7 +142,7 @@ public void addGroupWithoutTypeByTag() throws IOException {
}

@Test
public void removeGroupWithoutTypeAndTag() throws IOException {
public void removeGroupWithoutTypeAndTag() {
String groupName = "group1";
GroupItem item = new GroupItem(groupName, null);
item.addTag("Switchable");
Expand All @@ -157,7 +157,7 @@ public void removeGroupWithoutTypeAndTag() throws IOException {
}

@Test
public void updateSwitchable() throws IOException {
public void updateSwitchable() {
SwitchItem item = new SwitchItem("switch1");
item.setLabel("labelOld");
item.addTag("Switchable");
Expand Down Expand Up @@ -187,7 +187,7 @@ public void updateSwitchable() throws IOException {
}

@Test
public void changeSwitchState() throws IOException {
public void changeSwitchState() {

assertThat(((HueStatePlug) cs.ds.lights.get("1").state).on, is(false));

Expand All @@ -204,7 +204,7 @@ public void changeSwitchState() throws IOException {
}

@Test
public void changeGroupItemSwitchState() throws IOException {
public void changeGroupItemSwitchState() {

assertThat(((HueStatePlug) cs.ds.groups.get("10").action).on, is(false));

Expand All @@ -221,7 +221,7 @@ public void changeGroupItemSwitchState() throws IOException {
}

@Test
public void changeOnValue() throws IOException {
public void changeOnValue() {

assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));

Expand All @@ -235,7 +235,7 @@ public void changeOnValue() throws IOException {
}

@Test
public void changeOnAndBriValues() throws IOException {
public void changeOnAndBriValues() {

assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(0));
Expand All @@ -250,7 +250,7 @@ public void changeOnAndBriValues() throws IOException {
}

@Test
public void changeHueSatValues() throws IOException {
public void changeHueSatValues() {
HueLightEntry hueDevice = cs.ds.lights.get("2");
hueDevice.item.setState(OnOffType.ON);
hueDevice.state.as(HueStateColorBulb.class).on = true;
Expand All @@ -271,7 +271,7 @@ public void changeHueSatValues() throws IOException {
* Amazon echos are setting ct only, if commanded to turn a light white.
*/
@Test
public void changeCtValue() throws IOException {
public void changeCtValue() {
HueLightEntry hueDevice = cs.ds.lights.get("2");
hueDevice.item.setState(OnOffType.ON);
hueDevice.state.as(HueStateColorBulb.class).on = true;
Expand All @@ -292,7 +292,7 @@ public void changeCtValue() throws IOException {
}

@Test
public void switchOnWithXY() throws IOException {
public void switchOnWithXY() {
assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).on, is(false));
assertThat(((HueStateColorBulb) cs.ds.lights.get("2").state).bri, is(0));

Expand All @@ -313,8 +313,7 @@ public void switchOnWithXY() throws IOException {
}

@Test
public void allLightsAndSingleLight()
throws InterruptedException, ExecutionException, TimeoutException, IOException {
public void allLightsAndSingleLight() {
Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/lights").request().get();
assertEquals(200, response.getStatus());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public void tearDown() {
}

@Test
public void renameSensor() throws IOException {
public void renameSensor() {

assertThat(cs.ds.sensors.get("switch1").name, is("name1"));

Expand All @@ -107,7 +107,7 @@ public void renameSensor() throws IOException {
}

@Test
public void allAndSingleSensor() throws InterruptedException, ExecutionException, TimeoutException, IOException {
public void allAndSingleSensor() {
Response response = commonSetup.client.target(commonSetup.basePath + "/testuser/sensors").request().get();
assertEquals(200, response.getStatus());

Expand Down
Loading

0 comments on commit 97d8bc2

Please sign in to comment.