/*
* The MIT License
*
* Copyright 2014 sorrge.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.nyan.dch.communication.transport.tcpip.NAT;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sbbi.upnp.impls.InternetGatewayDevice;
import net.sbbi.upnp.messages.ActionResponse;
import net.sbbi.upnp.messages.UPNPResponseException;
/**
* This class fetches all {@link PortMapping} from an
* {@link InternetGatewayDevice}.
*
* @author chris
*/
class PortMappingExtractor
{
private static final Logger logger = Logger.getLogger(PortMappingExtractor.class.getName());
private final InternetGatewayDevice router;
private final Collection<PortMapping> mappings;
private boolean moreEntries;
private int currentMappingNumber;
private int nullPortMappings;
/**
* The maximum number of port mappings that we will try to retrieve from the
* router.
*/
private final int maxNumPortMappings;
PortMappingExtractor(final InternetGatewayDevice router,
final int maxNumPortMappings)
{
this.router = router;
this.maxNumPortMappings = maxNumPortMappings;
this.mappings = new LinkedList<>();
this.moreEntries = true;
this.currentMappingNumber = 0;
this.nullPortMappings = 0;
}
public Collection<PortMapping> getPortMappings() throws Exception
{
try
{
/*
* This is a little trick to get all port mappings. There is a
* method that gets the number of available port mappings
* (getNatMappingsCount()), but it seems, that this method just
* tries to get all port mappings and checks, if an error is
* returned.
*
* In order to speed this up, we will do the same here, but stop,
* when the first exception is thrown.
*/
while (morePortMappingsAvailable())
{
logger.log(Level.FINE, "Getting port mapping with entry number {0}...", currentMappingNumber);
try
{
final ActionResponse response = router
.getGenericPortMappingEntry(currentMappingNumber);
addResponse(response);
}
catch (final UPNPResponseException e)
{
handleUPNPResponseException(e);
}
currentMappingNumber++;
}
checkMaxNumPortMappingsReached();
}
catch (final IOException e)
{
throw new Exception("Could not get NAT mappings: "
+ e.getMessage(), e);
}
logger.log(Level.INFO, "Found {0} mappings, {1} mappings returned as null.", new Object[]{mappings.size(), nullPortMappings});
return mappings;
}
/**
* Check, if the max number of entries is reached and print a warning message.
*/
private void checkMaxNumPortMappingsReached()
{
if (currentMappingNumber == maxNumPortMappings)
logger.log(Level.WARNING, "Reached max number of port mappings to get ({0}). Perhaps not all port mappings where retrieved. Try to increase SBBIRouter.MAX_NUM_PORTMAPPINGS.", maxNumPortMappings);
}
private boolean morePortMappingsAvailable()
{
return moreEntries && currentMappingNumber < maxNumPortMappings;
}
private void addResponse(final ActionResponse response)
{
// Create a port mapping for the response.
if (response != null)
{
final PortMapping newMapping = PortMapping.create(response);
logger.log(Level.FINEST, "Got port mapping #{0}: {1}", new Object[]{currentMappingNumber, newMapping.getCompleteDescription()});
mappings.add(newMapping);
}
else
{
nullPortMappings++;
logger.log(Level.FINEST, "Got a null port mapping for number {0} ({1} so far).", new Object[]{currentMappingNumber, nullPortMappings});
}
}
private void handleUPNPResponseException(final UPNPResponseException e)
{
if (isNoMoreMappingsException(e))
{
moreEntries = false;
logger.log(Level.FINE, "Got no port mapping for entry number {0} (error code: {1}, error description: {2}). Stop getting more entries.", new Object[]{currentMappingNumber, e.getDetailErrorCode(), e.getDetailErrorDescription()});
}
else
{
moreEntries = false;
logger.log(Level.SEVERE, "Got exception when fetching port mapping for entry number {0}. Stop getting more entries.", + currentMappingNumber);
}
}
/**
* This method checks, if the error code of the given exception means, that no
* more mappings are available.
* <p>
* The following error codes are recognized:
* <ul>
* <li>SpecifiedArrayIndexInvalid: 713</li>
* <li>NoSuchEntryInArray: 714</li>
* <li>Invalid Args: 402 (e.g. for DD-WRT, TP-LINK TL-R460 firmware 4.7.6
* Build 100714 Rel.63134n)</li>
* <li>Other errors, e.g. "The reference to entity "T" must end with the ';'
* delimiter" or "Content is not allowed in prolog": 899 (e.g. ActionTec
* MI424-WR, Thomson TWG850-4U)</li>
* </ul>
* See bug reports
* <ul>
* <li><a href=
* "https://sourceforge.net/tracker/index.php?func=detail&aid=1939749&group_id=213879&atid=1027466"
* >https://sourceforge.net/tracker/index.php?func=detail&aid=
* 1939749&group_id=213879&atid=1027466</a></li>
* <li><a href="http://www.sbbi.net/forum/viewtopic.php?p=394">http://www.sbbi
* .net/forum/viewtopic.php?p=394</a></li>
* <li><a href=
* "http://sourceforge.net/tracker/?func=detail&atid=1027466&aid=3325388&group_id=213879"
* >http://sourceforge.net/tracker/?func=detail&atid=1027466&aid=3325388&
* group_id=213879</a></li>
* <a href=
* "https://sourceforge.net/tracker2/?func=detail&aid=2540478&group_id=213879&atid=1027466"
* >https://sourceforge.net/tracker2/?func=detail&aid=2540478&group_id=
* 213879&atid=1027466</a></li>
* </ul>
*
* @param e the exception to check
* @return <code>true</code>, if the given exception means, that no more port
* mappings are available, else <code>false</code>.
*/
private boolean isNoMoreMappingsException(final UPNPResponseException e)
{
final int errorCode = e.getDetailErrorCode();
switch (errorCode)
{
case 713:
case 714:
case 402:
case 899:
return true;
default:
return false;
}
}
}