From 10ba08ac8720eacfc2c102592d695320de39375c Mon Sep 17 00:00:00 2001 From: Emil Ivov Date: Thu, 27 Feb 2014 16:24:35 +0200 Subject: [PATCH] Moves ICE stat methods into a separste IceStats object. Continues ICE refactoring in view of the SIP support. --- .../impl/gui/main/call/CallInfoFrame.java | 329 +++++++++--------- .../jabber/IceUdpTransportManager.java | 216 ------------ .../jabber/RawUdpTransportManager.java | 167 --------- .../sip/CallPeerMediaHandlerSipImpl.java | 30 +- .../protocol/sip/TransportManagerSipImpl.java | 167 --------- .../protocol/media/CallPeerMediaHandler.java | 213 +----------- .../service/protocol/media/IceStats.java | 272 +++++++++++++++ .../protocol/media/TransportManager.java | 176 ++-------- 8 files changed, 499 insertions(+), 1071 deletions(-) create mode 100644 src/net/java/sip/communicator/service/protocol/media/IceStats.java diff --git a/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java b/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java index 3094622b7..393611882 100644 --- a/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java +++ b/src/net/java/sip/communicator/impl/gui/main/call/CallInfoFrame.java @@ -340,76 +340,87 @@ private void constructPeerInfo(CallPeer callPeer, StringBuffer stringBuffer) } stringBuffer.append("
"); - // ICE state - String iceState = callPeerMediaHandler.getICEState(); - if(iceState != null && !iceState.equals("Terminated")) - { - stringBuffer.append(getLineString( - resources.getI18NString( - "service.gui.callinfo.ICE_STATE"), - resources.getI18NString( - "service.gui.callinfo.ICE_STATE." - + iceState.toUpperCase()))); - } - stringBuffer.append("
"); - // Total harvesting time. - long harvestingTime - = callPeerMediaHandler.getTotalHarvestingTime(); - if(harvestingTime != 0) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.TOTAL_HARVESTING_TIME" - ), - harvestingTime - + " " - + resources.getI18NString( - "service.gui.callinfo.HARVESTING_MS_FOR") - + " " - + callPeerMediaHandler.getNbHarvesting() - + " " - + resources.getI18NString( - "service.gui.callinfo.HARVESTS"))); - } + IceStats iceStats = callPeerMediaHandler.getIceStats(); - // Current harvester time if ICE agent is harvesting. - String[] harvesterNames = + if (iceStats != null) { - "GoogleTurnCandidateHarvester", - "GoogleTurnSSLCandidateHarvester", - "HostCandidateHarvester", - "JingleNodesHarvester", - "StunCandidateHarvester", - "TurnCandidateHarvester", - "UPNPHarvester" - }; - for(int i = 0; i < harvesterNames.length; ++i) - { - harvestingTime = callPeerMediaHandler.getHarvestingTime( - harvesterNames[i]); - if(harvestingTime != 0) - { - stringBuffer.append(getLineString( - resources.getI18NString( - "service.gui.callinfo.HARVESTING_TIME") - + " " + harvesterNames[i], - harvestingTime - + " " - + resources.getI18NString( - "service.gui.callinfo.HARVESTING_MS_FOR" - ) - + " " - + callPeerMediaHandler.getNbHarvesting( - harvesterNames[i]) - + " " - + resources.getI18NString( - "service.gui.callinfo.HARVESTS"))); - } + stringBuffer.append(constructGenericIceStats(iceStats)); } } } } + /** + * Constructs a String with generic ICE information such as harvesting time. + * + * @param iceStats the {@link IceStats} that we should construct the string + * upon. + * + * @return a string with generic ICE stats + */ + private String constructGenericIceStats(IceStats iceStats) + { + StringBuffer statsBuff = new StringBuffer(""); + + // ICE state + String iceState = iceStats.getICEState(); + if(iceState != null && !iceState.equals("Terminated")) + { + statsBuff.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.ICE_STATE"), + resources.getI18NString( + "service.gui.callinfo.ICE_STATE." + + iceState.toUpperCase()))); + } + + statsBuff.append("
"); + // Total harvesting time. + long harvestingTime = iceStats.getTotalHarvestingTime(); + if(harvestingTime != 0) + { + statsBuff.append(getLineString(resources.getI18NString( + "service.gui.callinfo.TOTAL_HARVESTING_TIME" + ), + harvestingTime + " " + resources.getI18NString( + "service.gui.callinfo.HARVESTING_MS_FOR") + + " " + iceStats.getNbHarvesting() + + " " + resources.getI18NString( + "service.gui.callinfo.HARVESTS"))); + } + + // Current harvester time if ICE agent is harvesting. + String[] harvesterNames = + { + "GoogleTurnCandidateHarvester", + "GoogleTurnSSLCandidateHarvester", + "HostCandidateHarvester", + "JingleNodesHarvester", + "StunCandidateHarvester", + "TurnCandidateHarvester", + "UPNPHarvester" + }; + for(int i = 0; i < harvesterNames.length; ++i) + { + harvestingTime = iceStats.getHarvestingTime(harvesterNames[i]); + if(harvestingTime != 0) + { + statsBuff.append(getLineString( + resources.getI18NString( + "service.gui.callinfo.HARVESTING_TIME") + + " " + harvesterNames[i], harvestingTime + + " " + resources.getI18NString( + "service.gui.callinfo.HARVESTING_MS_FOR") + + " " + iceStats.getNbHarvesting(harvesterNames[i]) + + " " + resources.getI18NString( + "service.gui.callinfo.HARVESTS"))); + } + } + + return statsBuff.toString(); + } + /** * Constructs audio video peer info. * @@ -465,103 +476,15 @@ private void constructAudioVideoInfo( mediaStreamStats.getEncoding() + " / " + mediaStreamStats.getEncodingClockRate() + " Hz")); - boolean displayedIpPort = false; - - // ICE candidate type - String iceCandidateExtendedType = - callPeerMediaHandler.getICECandidateExtendedType( - mediaType.toString()); - if(iceCandidateExtendedType != null) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_CANDIDATE_EXTENDED_TYPE"), - iceCandidateExtendedType)); - displayedIpPort = true; - } - - // Local host address - InetSocketAddress iceLocalHostAddress = - callPeerMediaHandler.getICELocalHostAddress(mediaType.toString()); - if(iceLocalHostAddress != null) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_LOCAL_HOST_ADDRESS"), - iceLocalHostAddress.getAddress().getHostAddress() - + "/" + iceLocalHostAddress.getPort())); - displayedIpPort = true; - } - - // Local reflexive address - InetSocketAddress iceLocalReflexiveAddress = - callPeerMediaHandler.getICELocalReflexiveAddress( - mediaType.toString()); - if(iceLocalReflexiveAddress != null) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_LOCAL_REFLEXIVE_ADDRESS"), - iceLocalReflexiveAddress.getAddress() - .getHostAddress() - + "/" + iceLocalReflexiveAddress.getPort())); - displayedIpPort = true; - } - - // Local relayed address - InetSocketAddress iceLocalRelayedAddress = - callPeerMediaHandler.getICELocalRelayedAddress( - mediaType.toString()); - if(iceLocalRelayedAddress != null) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_LOCAL_RELAYED_ADDRESS"), - iceLocalRelayedAddress.getAddress() - .getHostAddress() - + "/" + iceLocalRelayedAddress.getPort())); - displayedIpPort = true; - } - - // Remote relayed address - InetSocketAddress iceRemoteRelayedAddress = - callPeerMediaHandler.getICERemoteRelayedAddress( - mediaType.toString()); - if(iceRemoteRelayedAddress != null) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_REMOTE_RELAYED_ADDRESS"), - iceRemoteRelayedAddress.getAddress() - .getHostAddress() - + "/" + iceRemoteRelayedAddress.getPort())); - displayedIpPort = true; - } - - // Remote reflexive address - InetSocketAddress iceRemoteReflexiveAddress = - callPeerMediaHandler.getICERemoteReflexiveAddress( - mediaType.toString()); - if(iceRemoteReflexiveAddress != null) - { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_REMOTE_REFLEXIVE_ADDRESS"), - iceRemoteReflexiveAddress.getAddress() - .getHostAddress() - + "/" + iceRemoteReflexiveAddress.getPort())); - displayedIpPort = true; - } + IceStats iceStats = callPeerMediaHandler.getIceStats(); - // Remote host address - InetSocketAddress iceRemoteHostAddress = - callPeerMediaHandler.getICERemoteHostAddress(mediaType.toString()); - if(iceRemoteHostAddress != null) + if(iceStats != null) { - stringBuffer.append(getLineString(resources.getI18NString( - "service.gui.callinfo.ICE_REMOTE_HOST_ADDRESS"), - iceRemoteHostAddress.getAddress().getHostAddress() - + "/" + iceRemoteHostAddress.getPort())); - displayedIpPort = true; + constructIceAddressingInfo(iceStats, mediaType); } - - // If the stream does not use ICE, then show the transport IP/port. - if(!displayedIpPort) + else { + // If the stream does not use ICE, then show the transport IP/port. stringBuffer.append( getLineString( resources.getI18NString("service.gui.callinfo.LOCAL_IP"), @@ -644,6 +567,102 @@ private void constructAudioVideoInfo( + (int) mediaStreamStats.getUploadJitterMs() + " ms")); } + /** + * Returns a string that contains formatted ICE stats information. + * + * @param iceStats the stats that we will be using to construct the info. + * + * @return the newly created string. + */ + private String constructIceAddressingInfo(IceStats iceStats, + MediaType mediaType) + { + StringBuffer stringBuffer = new StringBuffer(); + + // ICE candidate type + String iceCandidateExtendedType = + iceStats.getICECandidateExtendedType( + mediaType.toString()); + if(iceCandidateExtendedType != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_CANDIDATE_EXTENDED_TYPE"), + iceCandidateExtendedType)); + } + + // Local host address + InetSocketAddress iceLocalHostAddress + = iceStats.getICELocalHostAddress(mediaType.toString()); + if(iceLocalHostAddress != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_LOCAL_HOST_ADDRESS"), + iceLocalHostAddress.getAddress().getHostAddress() + + "/" + iceLocalHostAddress.getPort())); + } + + // Local reflexive address + InetSocketAddress iceLocalReflexiveAddress + = iceStats.getICELocalReflexiveAddress( mediaType.toString()); + if(iceLocalReflexiveAddress != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_LOCAL_REFLEXIVE_ADDRESS"), + iceLocalReflexiveAddress.getAddress() + .getHostAddress() + + "/" + iceLocalReflexiveAddress.getPort())); + } + + // Local relayed address + InetSocketAddress iceLocalRelayedAddress + = iceStats.getICELocalRelayedAddress(mediaType.toString()); + if(iceLocalRelayedAddress != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_LOCAL_RELAYED_ADDRESS"), + iceLocalRelayedAddress.getAddress() + .getHostAddress() + + "/" + iceLocalRelayedAddress.getPort())); + } + + // Remote relayed address + InetSocketAddress iceRemoteRelayedAddress + = iceStats.getICERemoteRelayedAddress( mediaType.toString()); + if(iceRemoteRelayedAddress != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_REMOTE_RELAYED_ADDRESS"), + iceRemoteRelayedAddress.getAddress() + .getHostAddress() + + "/" + iceRemoteRelayedAddress.getPort())); + } + + // Remote reflexive address + InetSocketAddress iceRemoteReflexiveAddress + = iceStats.getICERemoteReflexiveAddress(mediaType.toString()); + if(iceRemoteReflexiveAddress != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_REMOTE_REFLEXIVE_ADDRESS"), + iceRemoteReflexiveAddress.getAddress() + .getHostAddress() + + "/" + iceRemoteReflexiveAddress.getPort())); + } + + // Remote host address + InetSocketAddress iceRemoteHostAddress + = iceStats.getICERemoteHostAddress(mediaType.toString()); + if(iceRemoteHostAddress != null) + { + stringBuffer.append(getLineString(resources.getI18NString( + "service.gui.callinfo.ICE_REMOTE_HOST_ADDRESS"), + iceRemoteHostAddress.getAddress().getHostAddress() + + "/" + iceRemoteHostAddress.getPort())); + } + + return stringBuffer.toString(); + } + /** * Called when the title of the given CallPanel changes. * diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java index 318241931..9d9a2c383 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/IceUdpTransportManager.java @@ -1051,222 +1051,6 @@ public synchronized void close() } } - /** - * Returns the extended type of the candidate selected if this transport - * manager is using ICE. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return The extended type of the candidate selected if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public String getICECandidateExtendedType(String streamName) - { - return - TransportManager.getICECandidateExtendedType(iceAgent, streamName); - } - - /** - * Returns the current state of ICE processing. - * - * @return the current state of ICE processing. - */ - @Override - public String getICEState() - { - return iceAgent.getState().toString(); - } - - /** - * Returns the ICE local host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public InetSocketAddress getICELocalHostAddress(String streamName) - { - if(iceAgent != null) - { - LocalCandidate localCandidate - = iceAgent.getSelectedLocalCandidate(streamName); - - if(localCandidate != null) - return localCandidate.getHostAddress(); - } - return null; - } - - /** - * Returns the ICE remote host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public InetSocketAddress getICERemoteHostAddress(String streamName) - { - if(iceAgent != null) - { - RemoteCandidate remoteCandidate - = iceAgent.getSelectedRemoteCandidate(streamName); - - if(remoteCandidate != null) - return remoteCandidate.getHostAddress(); - } - return null; - } - - /** - * Returns the ICE local reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * local candidate used. - */ - @Override - public InetSocketAddress getICELocalReflexiveAddress(String streamName) - { - if(iceAgent != null) - { - LocalCandidate localCandidate - = iceAgent.getSelectedLocalCandidate(streamName); - - if(localCandidate != null) - return localCandidate.getReflexiveAddress(); - } - return null; - } - - /** - * Returns the ICE remote reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * remote candidate used. - */ - @Override - public InetSocketAddress getICERemoteReflexiveAddress(String streamName) - { - if(iceAgent != null) - { - RemoteCandidate remoteCandidate - = iceAgent.getSelectedRemoteCandidate(streamName); - - if(remoteCandidate != null) - return remoteCandidate.getReflexiveAddress(); - } - return null; - } - - /** - * Returns the ICE local relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * local candidate used. - */ - @Override - public InetSocketAddress getICELocalRelayedAddress(String streamName) - { - if(iceAgent != null) - { - LocalCandidate localCandidate - = iceAgent.getSelectedLocalCandidate(streamName); - - if(localCandidate != null) - return localCandidate.getRelayedAddress(); - } - return null; - } - - /** - * Returns the ICE remote relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * remote candidate used. - */ - @Override - public InetSocketAddress getICERemoteRelayedAddress(String streamName) - { - if(iceAgent != null) - { - RemoteCandidate remoteCandidate - = iceAgent.getSelectedRemoteCandidate(streamName); - - if(remoteCandidate != null) - return remoteCandidate.getRelayedAddress(); - } - return null; - } - - /** - * Returns the total harvesting time (in ms) for all harvesters. - * - * @return The total harvesting time (in ms) for all the harvesters. 0 if - * the ICE agent is null, or if the agent has nevers harvested. - */ - @Override - public long getTotalHarvestingTime() - { - return (iceAgent == null) ? 0 : iceAgent.getTotalHarvestingTime(); - } - - /** - * Returns the harvesting time (in ms) for the harvester given in parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The harvesting time (in ms) for the harvester given in parameter. - * 0 if this harvester does not exists, if the ICE agent is null, or if the - * agent has never harvested with this harvester. - */ - @Override - public long getHarvestingTime(String harvesterName) - { - return - (iceAgent == null) ? 0 : iceAgent.getHarvestingTime(harvesterName); - } - - /** - * Returns the number of harvesting for this agent. - * - * @return The number of harvesting for this agent. - */ - @Override - public int getNbHarvesting() - { - return (iceAgent == null) ? 0 : iceAgent.getHarvestCount(); - } - - /** - * Returns the number of harvesting time for the harvester given in - * parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The number of harvesting time for the harvester given in - * parameter. - */ - public int getNbHarvesting(String harvesterName) - { - return (iceAgent == null) ? 0 : iceAgent.getHarvestCount(harvesterName); - } - /** * Retransmit state change events from the Agent to the media handler. * @param evt the event for state change. diff --git a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java index ced48bbfb..395b76a0f 100644 --- a/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java +++ b/src/net/java/sip/communicator/impl/protocol/jabber/RawUdpTransportManager.java @@ -362,171 +362,4 @@ public List wrapupCandidateHarvest() { return local; } - - /** - * Returns the extended type of the candidate selected if this transport - * manager is using ICE. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return The extended type of the candidate selected if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public String getICECandidateExtendedType(String streamName) - { - return null; - } - - /** - * Returns the current state of ICE processing. - * - * @return the current state of ICE processing. - */ - @Override - public String getICEState() - { - return null; - } - - /** - * Returns the ICE local host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public InetSocketAddress getICELocalHostAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE remote host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public InetSocketAddress getICERemoteHostAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE local reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * local candidate used. - */ - @Override - public InetSocketAddress getICELocalReflexiveAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE remote reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * remote candidate used. - */ - @Override - public InetSocketAddress getICERemoteReflexiveAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE local relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * local candidate used. - */ - @Override - public InetSocketAddress getICELocalRelayedAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE remote relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * remote candidate used. - */ - @Override - public InetSocketAddress getICERemoteRelayedAddress(String streamName) - { - return null; - } - - /** - * Returns the total harvesting time (in ms) for all harvesters. - * - * @return The total harvesting time (in ms) for all the harvesters. 0 if - * the ICE agent is null, or if the agent has nevers harvested. - */ - @Override - public long getTotalHarvestingTime() - { - return 0; - } - - /** - * Returns the harvesting time (in ms) for the harvester given in parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The harvesting time (in ms) for the harvester given in parameter. - * 0 if this harvester does not exists, if the ICE agent is null, or if the - * agent has never harvested with this harvester. - */ - @Override - public long getHarvestingTime(String harvesterName) - { - return 0; - } - - /** - * Returns the number of harvesting for this agent. - * - * @return The number of harvesting for this agent. - */ - @Override - public int getNbHarvesting() - { - return 0; - } - - /** - * Returns the number of harvesting time for the harvester given in - * parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The number of harvesting time for the harvester given in - * parameter. - */ - @Override - public int getNbHarvesting(String harvesterName) - { - return 0; - } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java index ea18c7b14..46d978b35 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/CallPeerMediaHandlerSipImpl.java @@ -44,6 +44,10 @@ public class CallPeerMediaHandlerSipImpl */ private static final String DTLS_SRTP_FINGERPRINT_ATTR = "fingerprint"; + /** + * The name of the SDP attribute which specifies the role of the local + * agent in DTLS/SRTP negotiation. + */ private static final String DTLS_SRTP_SETUP_ATTR = "setup"; /** @@ -99,7 +103,11 @@ public CallPeerMediaHandlerSipImpl(CallPeerSipImpl peer) { super(peer, peer); - transportManager = new TransportManagerSipImpl(peer); + if(isUseIce()) + transportManager = new IceTransportManagerSipImpl(peer); + else + transportManager = new TransportManagerSipImpl(peer); + qualityControls = new QualityControlWrapper(peer); } @@ -156,9 +164,10 @@ private SessionDescription createFirstOffer() userName, mediaDescs); - //ICE HACK - please fix - new IceTransportManagerSipImpl(getPeer()).startCandidateHarvest( - sDes, null, false, false, false, false, false ); + //in case we are using ICE, start the harvest now. this would have + //no effect otherwise. + //getTransportManager().startCandidateHarvest( + // sDes, null, false, false, false, false, false); this.localSess = sDes; return localSess; @@ -1838,4 +1847,17 @@ private boolean isDtlsMediaDescription(MediaDescription mediaDescription) } return dtls; } + + /** + * Determines if this account is supposed to use ICE. + * + * @return true if this account is supposed to use ICE and + * false otherwise. + */ + private boolean isUseIce() + { + return getPeer().getProtocolProvider().getAccountID() + .getAccountPropertyBoolean( + ProtocolProviderFactory.IS_USE_ICE, false); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/sip/TransportManagerSipImpl.java b/src/net/java/sip/communicator/impl/protocol/sip/TransportManagerSipImpl.java index 57adba7fd..cf17fba23 100644 --- a/src/net/java/sip/communicator/impl/protocol/sip/TransportManagerSipImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/sip/TransportManagerSipImpl.java @@ -49,171 +49,4 @@ protected InetAddress getIntendedDestination(CallPeerSipImpl peer) return peer.getProtocolProvider() .getIntendedDestination(peer.getPeerAddress()).getAddress(); } - - /** - * Returns the extended type of the candidate selected if this transport - * manager is using ICE. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return The extended type of the candidate selected if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public String getICECandidateExtendedType(String streamName) - { - return null; - } - - /** - * Returns the current state of ICE processing. - * - * @return the current state of ICE processing. - */ - @Override - public String getICEState() - { - return null; - } - - /** - * Returns the ICE local host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public InetSocketAddress getICELocalHostAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE remote host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - @Override - public InetSocketAddress getICERemoteHostAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE local reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * local candidate used. - */ - @Override - public InetSocketAddress getICELocalReflexiveAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE remote reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * remote candidate used. - */ - @Override - public InetSocketAddress getICERemoteReflexiveAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE local relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * local candidate used. - */ - @Override - public InetSocketAddress getICELocalRelayedAddress(String streamName) - { - return null; - } - - /** - * Returns the ICE remote relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * remote candidate used. - */ - @Override - public InetSocketAddress getICERemoteRelayedAddress(String streamName) - { - return null; - } - - /** - * Returns the total harvesting time (in ms) for all harvesters. - * - * @return The total harvesting time (in ms) for all the harvesters. 0 if - * the ICE agent is null, or if the agent has nevers harvested. - */ - @Override - public long getTotalHarvestingTime() - { - return 0; - } - - /** - * Returns the harvesting time (in ms) for the harvester given in parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The harvesting time (in ms) for the harvester given in parameter. - * 0 if this harvester does not exists, if the ICE agent is null, or if the - * agent has never harvested with this harvester. - */ - @Override - public long getHarvestingTime(String harvesterName) - { - return 0; - } - - /** - * Returns the number of harvesting for this agent. - * - * @return The number of harvesting for this agent. - */ - @Override - public int getNbHarvesting() - { - return 0; - } - - /** - * Returns the number of harvesting time for the harvester given in - * parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The number of harvesting time for the harvester given in - * parameter. - */ - @Override - public int getNbHarvesting(String harvesterName) - { - return 0; - } } diff --git a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java index 788dc60af..4cb7ddcec 100644 --- a/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java +++ b/src/net/java/sip/communicator/service/protocol/media/CallPeerMediaHandler.java @@ -653,169 +653,18 @@ protected List getExtensionsForType(MediaType type) } /** - * Returns the harvesting time (in ms) for the harvester given in parameter. + * Returns the current instance of {@link IceStats} collecting information + * about how ICE negotiation went. * - * @param harvesterName The class name if the harvester. - * - * @return The harvesting time (in ms) for the harvester given in parameter. - * 0 if this harvester does not exists, if the ICE agent is null, or if the - * agent has never harvested with this harvester. - */ - public long getHarvestingTime(String harvesterName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getHarvestingTime(harvesterName); - } - - /** - * Returns the extended type of the candidate selected if this transport - * manager is using ICE. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return The extended type of the candidate selected if this transport - * manager is using ICE. Otherwise, returns null. - */ - public String getICECandidateExtendedType(String streamName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICECandidateExtendedType(streamName); - } - - /** - * Returns the ICE local host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - public InetSocketAddress getICELocalHostAddress(String streamName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICELocalHostAddress(streamName); - } - - /** - * Returns the ICE local reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * local candidate used. - */ - public InetSocketAddress getICELocalReflexiveAddress(String streamName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICELocalReflexiveAddress(streamName); - } - - /** - * Returns the ICE local relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * local candidate used. - */ - public InetSocketAddress getICELocalRelayedAddress(String streamName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICELocalRelayedAddress(streamName); - } - - /** - * Returns the ICE remote host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - public InetSocketAddress getICERemoteHostAddress(String streamName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICERemoteHostAddress(streamName); - } - - /** - * Returns the ICE remote reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * remote candidate used. - */ - public InetSocketAddress getICERemoteReflexiveAddress(String streamName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICERemoteReflexiveAddress(streamName); - } - - /** - * Returns the ICE remote relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * remote candidate used. + * @return the instance of {@link IceStats} that we are currently using. */ - public InetSocketAddress getICERemoteRelayedAddress(String streamName) + public IceStats getIceStats() { TransportManager transportManager = queryTransportManager(); - return - (transportManager == null) + return (transportManager == null) ? null - : transportManager.getICERemoteRelayedAddress(streamName); - } - - /** - * Returns the current state of ICE processing. - * - * @return the current state of ICE processing if this transport - * manager is using ICE. Otherwise, returns null. - */ - public String getICEState() - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getICEState(); + : transportManager.getIceStats(); } /** @@ -917,40 +766,6 @@ public MediaHandler getMediaHandler() return mediaHandler; } - /** - * Returns the number of harvesting for this agent. - * - * @return The number of harvesting for this agent. - */ - public int getNbHarvesting() - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getNbHarvesting(); - } - - /** - * Returns the number of harvesting time for the harvester given in - * parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The number of harvesting time for the harvester given in - * parameter. - */ - public int getNbHarvesting(String harvesterName) - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getNbHarvesting(harvesterName); - } - /** * Returns the peer that is this media handler's "raison d'etre". * @@ -1021,22 +836,6 @@ public MediaStream getStream(MediaType mediaType) } } - /** - * Returns the total harvesting time (in ms) for all harvesters. - * - * @return The total harvesting time (in ms) for all the harvesters. 0 if - * the ICE agent is null, or if the agent has nevers harvested. - */ - public long getTotalHarvestingTime() - { - TransportManager transportManager = queryTransportManager(); - - return - (transportManager == null) - ? null - : transportManager.getTotalHarvestingTime(); - } - /** * Gets the TransportManager implementation handling our address * management. If the TransportManager does not exist yet, it is diff --git a/src/net/java/sip/communicator/service/protocol/media/IceStats.java b/src/net/java/sip/communicator/service/protocol/media/IceStats.java new file mode 100644 index 000000000..80e00342b --- /dev/null +++ b/src/net/java/sip/communicator/service/protocol/media/IceStats.java @@ -0,0 +1,272 @@ +/* + * Jitsi, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.service.protocol.media; + +import org.ice4j.ice.*; + +import java.net.*; + +/** + * Contains some basic ICE statistics that can be displayed to users during + * calls. + * + * @author Emil Ivov + */ +public class IceStats +{ + /** + * The ICE agent whose statistics we will be retrieving. + */ + private final Agent iceAgent; + + /** + * Instantiate ICE statistics for the specified iceAgent + * + * @param iceAgent the agent that we'd like to return statistics on. + */ + public IceStats(Agent iceAgent) + { + this.iceAgent = iceAgent; + } + + /** + * Returns the ICE candidate extended type selected by the given agent. + * + * @param iceAgent The ICE agent managing the ICE offer/answer exchange, + * collecting and selecting the candidate. + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return The ICE candidate extended type selected by the given agent. null + * if the iceAgent is null or if there is no candidate selected or + * available. + */ + private static String getICECandidateExtendedType( Agent iceAgent, + String streamName) + { + if(iceAgent != null) + { + LocalCandidate localCandidate + = iceAgent.getSelectedLocalCandidate(streamName); + + if(localCandidate != null) + return localCandidate.getExtendedType().toString(); + } + return null; + } + + + /** + * Returns the extended type of the selected candidate or null + * if we are not using ICE.. + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return The extended type of the selected candidate or null if + * we are not using ICE. + */ + public String getICECandidateExtendedType(String streamName) + { + if (iceAgent == null) + return null; + + return + IceStats.getICECandidateExtendedType(iceAgent, streamName); + } + + /** + * Returns the current state of ICE processing. + * + * @return the current state of ICE processing. + */ + public String getICEState() + { + if (iceAgent == null) + return null; + + return iceAgent.getState().toString(); + } + + /** + * Returns the ICE local host address. + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE local host address if this transport + * manager is using ICE. Otherwise, returns null. + */ + public InetSocketAddress getICELocalHostAddress(String streamName) + { + if(iceAgent != null) + { + LocalCandidate localCandidate + = iceAgent.getSelectedLocalCandidate(streamName); + + if(localCandidate != null) + return localCandidate.getHostAddress(); + } + return null; + } + + /** + * Returns the ICE remote host address. + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE remote host address if this transport + * manager is using ICE. Otherwise, returns null. + */ + public InetSocketAddress getICERemoteHostAddress(String streamName) + { + if(iceAgent != null) + { + RemoteCandidate remoteCandidate + = iceAgent.getSelectedRemoteCandidate(streamName); + + if(remoteCandidate != null) + return remoteCandidate.getHostAddress(); + } + return null; + } + + /** + * Returns the ICE local reflexive address (server or peer reflexive). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE local reflexive address. May be null if this transport + * manager is not using ICE or if there is no reflexive address for the + * local candidate used. + */ + public InetSocketAddress getICELocalReflexiveAddress(String streamName) + { + if(iceAgent != null) + { + LocalCandidate localCandidate + = iceAgent.getSelectedLocalCandidate(streamName); + + if(localCandidate != null) + return localCandidate.getReflexiveAddress(); + } + return null; + } + + /** + * Returns the ICE remote reflexive address (server or peer reflexive). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE remote reflexive address. May be null if this transport + * manager is not using ICE or if there is no reflexive address for the + * remote candidate used. + */ + public InetSocketAddress getICERemoteReflexiveAddress(String streamName) + { + if(iceAgent != null) + { + RemoteCandidate remoteCandidate + = iceAgent.getSelectedRemoteCandidate(streamName); + + if(remoteCandidate != null) + return remoteCandidate.getReflexiveAddress(); + } + return null; + } + + /** + * Returns the ICE local relayed address (server or peer relayed). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE local relayed address. May be null if this transport + * manager is not using ICE or if there is no relayed address for the + * local candidate used. + */ + public InetSocketAddress getICELocalRelayedAddress(String streamName) + { + if(iceAgent != null) + { + LocalCandidate localCandidate + = iceAgent.getSelectedLocalCandidate(streamName); + + if(localCandidate != null) + return localCandidate.getRelayedAddress(); + } + return null; + } + + /** + * Returns the ICE remote relayed address (server or peer relayed). + * + * @param streamName The stream name (AUDIO, VIDEO); + * + * @return the ICE remote relayed address. May be null if this transport + * manager is not using ICE or if there is no relayed address for the + * remote candidate used. + */ + public InetSocketAddress getICERemoteRelayedAddress(String streamName) + { + if(iceAgent != null) + { + RemoteCandidate remoteCandidate + = iceAgent.getSelectedRemoteCandidate(streamName); + + if(remoteCandidate != null) + return remoteCandidate.getRelayedAddress(); + } + return null; + } + + /** + * Returns the total harvesting time (in ms) for all harvesters. + * + * @return The total harvesting time (in ms) for all the harvesters. 0 if + * the ICE agent is null, or if the agent has nevers harvested. + */ + public long getTotalHarvestingTime() + { + return (iceAgent == null) ? 0 : iceAgent.getTotalHarvestingTime(); + } + + /** + * Returns the harvesting time (in ms) for the harvester given in parameter. + * + * @param harvesterName The class name if the harvester. + * + * @return The harvesting time (in ms) for the harvester given in parameter. + * 0 if this harvester does not exists, if the ICE agent is null, or if the + * agent has never harvested with this harvester. + */ + public long getHarvestingTime(String harvesterName) + { + return + (iceAgent == null) ? 0 : iceAgent.getHarvestingTime(harvesterName); + } + + /** + * Returns the number of harvesting for this agent. + * + * @return The number of harvesting for this agent. + */ + public int getNbHarvesting() + { + return (iceAgent == null) ? 0 : iceAgent.getHarvestCount(); + } + + /** + * Returns the number of harvesting time for the harvester given in + * parameter. + * + * @param harvesterName The class name if the harvester. + * + * @return The number of harvesting time for the harvester given in + * parameter. + */ + public int getNbHarvesting(String harvesterName) + { + return (iceAgent == null) ? 0 : iceAgent.getHarvestCount(harvesterName); + } +} diff --git a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java index 053aadf41..dfd83fe57 100644 --- a/src/net/java/sip/communicator/service/protocol/media/TransportManager.java +++ b/src/net/java/sip/communicator/service/protocol/media/TransportManager.java @@ -122,6 +122,11 @@ public abstract class TransportManager> private final StreamConnector[] streamConnectors = new StreamConnector[MediaType.values().length]; + /** + * Statistics container for connectivity debugging. + */ + private IceStats iceStats = null; + /** * Creates a new instance of this transport manager, binding it to the * specified peer. @@ -247,8 +252,7 @@ protected void closeStreamConnector( protected StreamConnector createStreamConnector(MediaType mediaType) throws OperationFailedException { - NetworkAddressManagerService nam - = ProtocolMediaActivator.getNetworkAddressManagerService(); + NetworkAddressManagerService nam = getNetAddrMgr(); InetAddress intendedDestination = getIntendedDestination(getCallPeer()); InetAddress localHostForPeer = nam.getLocalHost(intendedDestination); @@ -393,8 +397,7 @@ public InetAddress getLastUsedLocalHost() return streamConnector.getDataSocket().getLocalAddress(); } - NetworkAddressManagerService nam - = ProtocolMediaActivator.getNetworkAddressManagerService(); + NetworkAddressManagerService nam = getNetAddrMgr(); InetAddress intendedDestination = getIntendedDestination(getCallPeer()); return nam.getLocalHost(intendedDestination); @@ -647,157 +650,6 @@ protected static PortTracker getPortTracker(String mediaTypeStr) } } - /** - * Returns the extended type of the candidate selected if this transport - * manager is using ICE. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return The extended type of the candidate selected if this transport - * manager is using ICE. Otherwise, returns null. - */ - public abstract String getICECandidateExtendedType(String streamName); - - /** - * Returns the current state of ICE processing. - * - * @return the current state of ICE processing if this transport - * manager is using ICE. Otherwise, returns null. - */ - public abstract String getICEState(); - - /** - * Returns the ICE local host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - public abstract InetSocketAddress getICELocalHostAddress(String streamName); - - /** - * Returns the ICE remote host address. - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote host address if this transport - * manager is using ICE. Otherwise, returns null. - */ - public abstract InetSocketAddress getICERemoteHostAddress( - String streamName); - - /** - * Returns the ICE local reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * local candidate used. - */ - public abstract InetSocketAddress getICELocalReflexiveAddress( - String streamName); - - /** - * Returns the ICE remote reflexive address (server or peer reflexive). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote reflexive address. May be null if this transport - * manager is not using ICE or if there is no reflexive address for the - * remote candidate used. - */ - public abstract InetSocketAddress getICERemoteReflexiveAddress( - String streamName); - - /** - * Returns the ICE local relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE local relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * local candidate used. - */ - public abstract InetSocketAddress getICELocalRelayedAddress( - String streamName); - - /** - * Returns the ICE remote relayed address (server or peer relayed). - * - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return the ICE remote relayed address. May be null if this transport - * manager is not using ICE or if there is no relayed address for the - * remote candidate used. - */ - public abstract InetSocketAddress getICERemoteRelayedAddress( - String streamName); - - /** - * Returns the total harvesting time (in ms) for all harvesters. - * - * @return The total harvesting time (in ms) for all the harvesters. 0 if - * the ICE agent is null, or if the agent has nevers harvested. - */ - public abstract long getTotalHarvestingTime(); - - /** - * Returns the harvesting time (in ms) for the harvester given in parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The harvesting time (in ms) for the harvester given in parameter. - * 0 if this harvester does not exists, if the ICE agent is null, or if the - * agent has never harvested with this harvester. - */ - public abstract long getHarvestingTime(String harvesterName); - - /** - * Returns the number of harvesting for this agent. - * - * @return The number of harvesting for this agent. - */ - public abstract int getNbHarvesting(); - - /** - * Returns the number of harvesting time for the harvester given in - * parameter. - * - * @param harvesterName The class name if the harvester. - * - * @return The number of harvesting time for the harvester given in - * parameter. - */ - public abstract int getNbHarvesting(String harvesterName); - - /** - * Returns the ICE candidate extended type selected by the given agent. - * - * @param iceAgent The ICE agent managing the ICE offer/answer exchange, - * collecting and selecting the candidate. - * @param streamName The stream name (AUDIO, VIDEO); - * - * @return The ICE candidate extended type selected by the given agent. null - * if the iceAgent is null or if there is no candidate selected or - * available. - */ - public static String getICECandidateExtendedType( - Agent iceAgent, - String streamName) - { - if(iceAgent != null) - { - LocalCandidate localCandidate - = iceAgent.getSelectedLocalCandidate(streamName); - - if(localCandidate != null) - return localCandidate.getExtendedType().toString(); - } - return null; - } - /** * Discovers and returns a list of dynamically obtained (as opposed to * statically configured) STUN/TURN servers for use with this account. This @@ -912,6 +764,9 @@ protected Agent createIceAgent() if(accID.isUPNPEnabled()) agent.addCandidateHarvester(new UPNPHarvester()); + //update our stats container + this.iceStats = new IceStats(agent); + return agent; } @@ -946,4 +801,15 @@ public static NetworkAddressManagerService getNetAddrMgr() { return ProtocolMediaActivator.getNetworkAddressManagerService(); } + + /** + * Returns the current instance of {@link IceStats} collecting information + * about how ICE negotiation went. + * + * @return the instance of {@link IceStats} that we are currently using. + */ + public IceStats getIceStats() + { + return iceStats; + } }