All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lastbamboo.common.turn.client.TurnStunProtocolEncoder Maven / Gradle / Ivy

package org.lastbamboo.common.turn.client;

import java.net.InetSocketAddress;

import org.apache.commons.id.uuid.UUID;
import org.littleshoot.mina.common.ByteBuffer;
import org.littleshoot.mina.common.IoSession;
import org.littleshoot.mina.filter.codec.ProtocolEncoderOutput;
import org.littleshoot.stun.stack.encoder.StunMessageEncoder;
import org.littleshoot.stun.stack.message.BindingErrorResponse;
import org.littleshoot.stun.stack.message.BindingRequest;
import org.littleshoot.stun.stack.message.BindingSuccessResponse;
import org.littleshoot.stun.stack.message.CanceledStunMessage;
import org.littleshoot.stun.stack.message.ConnectErrorStunMessage;
import org.littleshoot.stun.stack.message.NullStunMessage;
import org.littleshoot.stun.stack.message.StunMessage;
import org.littleshoot.stun.stack.message.StunMessageVisitor;
import org.littleshoot.stun.stack.message.turn.AllocateErrorResponse;
import org.littleshoot.stun.stack.message.turn.AllocateRequest;
import org.littleshoot.stun.stack.message.turn.AllocateSuccessResponse;
import org.littleshoot.stun.stack.message.turn.ConnectRequest;
import org.littleshoot.stun.stack.message.turn.ConnectionStatusIndication;
import org.littleshoot.stun.stack.message.turn.DataIndication;
import org.littleshoot.stun.stack.message.turn.SendIndication;
import org.littleshoot.util.mina.DemuxableProtocolEncoder;
import org.littleshoot.util.mina.MinaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Encodes bytes into STUN messages with the additional step of wrapping 
 * certain messages in TURN Send Indications.  This will in particular
 * wrap messages that are only written when wrapping is necessary, such as
 * Binding Responses used with ICE.  
 */
public class TurnStunProtocolEncoder implements DemuxableProtocolEncoder
    {

    private final Logger m_log = LoggerFactory.getLogger(getClass());

    public void dispose(final IoSession session) throws Exception
        {

        }

    public void encode(final IoSession session, final Object msg,
        final ProtocolEncoderOutput out) throws Exception
        {
        m_log.debug("Encoding TURN/STUN message: {}", msg);
        final StunMessage stunMessage = (StunMessage) msg;
        final TurnStunMessageMapper mapper =
            (TurnStunMessageMapper) session.getAttribute(
                "REMOTE_ADDRESS_MAP");
        final StunMessageVisitor visitor = 
            new SendIndicationStunMessageVisitor(out, mapper);
        stunMessage.accept(visitor);
        }
    
    private final class SendIndicationStunMessageVisitor
        implements StunMessageVisitor
        {
        
        private final ProtocolEncoderOutput m_out;
        private final TurnStunMessageMapper m_mapper;

        private SendIndicationStunMessageVisitor(
            final ProtocolEncoderOutput out, 
            final TurnStunMessageMapper mapper)
            {
            m_out = out;
            m_mapper = mapper;
            }

        private void wrapInSendIndication(final StunMessage msg)
            {
            final StunMessageEncoder encoder = new StunMessageEncoder();
            final ByteBuffer buf = encoder.encode(msg);
            
            final InetSocketAddress remoteAddress = m_mapper.get(msg);
            if (remoteAddress == null)
                {
                m_log.warn("No matching transaction ID for: {} in {}", msg, 
                    m_mapper);
                return;
                }
            final byte[] bytes = MinaUtils.toByteArray(buf);
            m_log.debug("Sending TCP framed data of length: {}", bytes.length);
            final SendIndication indication = 
                new SendIndication(remoteAddress, bytes);
            final ByteBuffer indicationBuf = encoder.encode(indication);
            m_out.write(indicationBuf);
            }
        
        private void noWrap(final StunMessage msg)
            {
            final StunMessageEncoder encoder = new StunMessageEncoder();
            final ByteBuffer buf = encoder.encode(msg);
            if (buf == null)
                {
                m_log.error("Null buffer for message: {}", msg);
                }
            else
                {
                m_out.write(buf);
                }
            }

        public ByteBuffer visitAllocateRequest(final AllocateRequest request)
            {
            noWrap(request);
            return null;
            }

        public ByteBuffer visitBindingErrorResponse(
            final BindingErrorResponse response)
            {
            wrapInSendIndication(response);
            return null;
            }

        public ByteBuffer visitBindingRequest(final BindingRequest binding)
            {
            wrapInSendIndication(binding);
            return null;
            }

        public ByteBuffer visitBindingSuccessResponse(
            final BindingSuccessResponse response)
            {
            // OK, this is weird.  Because we're using TURN here, the mapped
            // address by default is the address of the TURN server.  That's
            // not right, though -- the mapped address should be the address
            // *as if from the perspective of the TURN server*, or, i.e.,
            // the remote address from the data indication.  So we need to
            // create a new response here using the remote address from the
            // data indication, overwriting the original.
            final InetSocketAddress remoteAddress = m_mapper.get(response);
            if (remoteAddress == null)
                {
                m_log.warn("No matching transaction ID for: {}", response);
                return null;
                }      
            final UUID transactionId = response.getTransactionId();
            final StunMessage turnResponse = 
                new BindingSuccessResponse(transactionId.getRawBytes(), 
                    remoteAddress);
            wrapInSendIndication(turnResponse);
            return null;
            }

        public ByteBuffer visitCanceledMessage(
            final CanceledStunMessage message)
            {
            noWrap(message);
            return null;
            }

        public ByteBuffer visitSendIndication(final SendIndication request)
            {
            // This is a weird case.  Other protocols, such as TCP framing,
            // may already wrap their data in Send Indications.
            m_log.debug("Writing send indication...");
            noWrap(request);
            return null;
            }

        public ByteBuffer visitAllocateErrorResponse(
            final AllocateErrorResponse response)
            {
            m_log.warn("Unexpected message: {}", response);
            return null;
            }
        
        public ByteBuffer visitAllocateSuccessResponse(
            final AllocateSuccessResponse response)
            {
            m_log.warn("Unexpected message: {}", response);
            return null;
            }
        
        public ByteBuffer visitConnectErrorMesssage(
            final ConnectErrorStunMessage message)
            {
            m_log.warn("Unexpected message: {}", message);
            return null;
            }

        public ByteBuffer visitConnectRequest(final ConnectRequest request)
            {
            m_log.warn("Unexpected message: {}", request);
            return null;
            }

        public ByteBuffer visitConnectionStatusIndication(
            final ConnectionStatusIndication indication)
            {
            m_log.warn("Unexpected message: {}", indication);
            return null;
            }

        public ByteBuffer visitDataIndication(final DataIndication data)
            {
            m_log.warn("Unexpected message: {}", data);
            return null;
            }

        public ByteBuffer visitNullMessage(final NullStunMessage message)
            {
            m_log.warn("Unexpected message: {}", message);
            return null;
            }
        }
    
    @Override
    public String toString()
        {
        return getClass().getSimpleName();
        }
    
    }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy