This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.net.icep.attr.*;
|
||||
import com.wentch.redkale.net.icep.stun.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class BindingIcepServlet extends IcepServlet {
|
||||
|
||||
@Override
|
||||
public void execute(IcepRequest request, IcepResponse response) throws IOException {
|
||||
StunPacket packet = request.getStunPacket();
|
||||
ByteBuffer buffer = response.getContext().pollBuffer();
|
||||
packet.addAttribute(new XorMappedAddressAttribute(request.getRemoteAddress()));
|
||||
packet.getHeader().setRequestid((StunHeader.TYPE_SUCCESS | StunHeader.ACTION_BINDING));
|
||||
packet.encode(buffer);
|
||||
buffer.flip();
|
||||
Utility.println("响应结果: ", buffer);
|
||||
response.finish(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getRequestid() {
|
||||
return 0x0001;
|
||||
}
|
||||
|
||||
}
|
||||
20
src-plugin/com/wentch/redkale/net/icep/IcepCoder.java
Normal file
20
src-plugin/com/wentch/redkale/net/icep/IcepCoder.java
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
* @param <T>
|
||||
*/
|
||||
public interface IcepCoder<T> {
|
||||
|
||||
public T decode(final ByteBuffer buffer);
|
||||
|
||||
public ByteBuffer encode(final ByteBuffer buffer);
|
||||
}
|
||||
33
src-plugin/com/wentch/redkale/net/icep/IcepContext.java
Normal file
33
src-plugin/com/wentch/redkale/net/icep/IcepContext.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.convert.bson.*;
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import com.wentch.redkale.watch.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
import java.nio.charset.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class IcepContext extends Context {
|
||||
|
||||
protected final BsonFactory bsonFactory;
|
||||
|
||||
public IcepContext(long serverStartTime, Logger logger, ExecutorService executor, ObjectPool<ByteBuffer> bufferPool,
|
||||
ObjectPool<Response> responsePool, int maxbody, Charset charset, InetSocketAddress address, PrepareServlet prepare,
|
||||
WatchFactory watch, int readTimeoutSecond, int writeTimeoutSecond) {
|
||||
super(serverStartTime, logger, executor, bufferPool, responsePool, maxbody, charset,
|
||||
address, prepare, watch, readTimeoutSecond, writeTimeoutSecond);
|
||||
this.bsonFactory = BsonFactory.root();
|
||||
}
|
||||
}
|
||||
29
src-plugin/com/wentch/redkale/net/icep/IcepException.java
Normal file
29
src-plugin/com/wentch/redkale/net/icep/IcepException.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class IcepException extends RuntimeException {
|
||||
|
||||
public IcepException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public IcepException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public IcepException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public IcepException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class IcepPrepareServlet extends PrepareServlet<IcepRequest, IcepResponse> {
|
||||
|
||||
private final HashMap<Short, IcepServlet> servletmaps = new HashMap<>();
|
||||
|
||||
public IcepPrepareServlet() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Context context, AnyValue config) {
|
||||
}
|
||||
|
||||
public void addIcepServlet(IcepServlet servlet, AnyValue conf) {
|
||||
servlet.conf = conf;
|
||||
this.servletmaps.put(servlet.getRequestid(), servlet);
|
||||
}
|
||||
|
||||
// 28.[00,03,00,08, 21,12,a4,42,45,6f,4e,77,4e,47,71,55,32,37,77,39, 00,19,00,04,11,00,00,00]
|
||||
@Override
|
||||
public void execute(IcepRequest request, IcepResponse response) throws IOException {
|
||||
IcepServlet servlet = servletmaps.get(request.getRequestid());
|
||||
if (servlet != null) {
|
||||
servlet.execute(request, response);
|
||||
} else {
|
||||
response.finish();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
69
src-plugin/com/wentch/redkale/net/icep/IcepRequest.java
Normal file
69
src-plugin/com/wentch/redkale/net/icep/IcepRequest.java
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.net.icep.stun.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class IcepRequest extends Request {
|
||||
|
||||
private short requestid;
|
||||
|
||||
private StunPacket stunPacket;
|
||||
|
||||
protected IcepRequest(IcepContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
// 20.[00,01,00,00,21,12,a4,42,63,6a,30,65,62,43,44,57,67,76,68,58]
|
||||
@Override
|
||||
protected int readHeader(ByteBuffer buffer) {
|
||||
Utility.println(Utility.now() + "-------" + getRemoteAddress() + " ", buffer);
|
||||
if (buffer.remaining() < 20) return -1;
|
||||
this.requestid = buffer.getShort();
|
||||
char bodysize = buffer.getChar();
|
||||
byte[] bytes = new byte[16];
|
||||
buffer.get(bytes);
|
||||
StunHeader header = new StunHeader(this.requestid, bytes);
|
||||
this.stunPacket = new StunPacket(header);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) channel.getRemoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readBody(ByteBuffer buffer) {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void prepare() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void recycle() {
|
||||
this.requestid = 0;
|
||||
this.stunPacket = null;
|
||||
super.recycle();
|
||||
}
|
||||
|
||||
public short getRequestid() {
|
||||
return requestid;
|
||||
}
|
||||
|
||||
public StunPacket getStunPacket() {
|
||||
return stunPacket;
|
||||
}
|
||||
|
||||
}
|
||||
25
src-plugin/com/wentch/redkale/net/icep/IcepResponse.java
Normal file
25
src-plugin/com/wentch/redkale/net/icep/IcepResponse.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class IcepResponse extends Response<IcepRequest> {
|
||||
|
||||
protected IcepResponse(Context context, IcepRequest request) {
|
||||
super(context, request);
|
||||
}
|
||||
|
||||
public static ObjectPool<Response> createPool(AtomicLong creatCounter, AtomicLong cycleCounter, int max, Creator<Response> creator) {
|
||||
return new ObjectPool<>(creatCounter, cycleCounter, max, creator, (x) -> ((IcepResponse) x).prepare(), (x) -> ((IcepResponse) x).recycle());
|
||||
}
|
||||
}
|
||||
58
src-plugin/com/wentch/redkale/net/icep/IcepServer.java
Normal file
58
src-plugin/com/wentch/redkale/net/icep/IcepServer.java
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import com.wentch.redkale.watch.*;
|
||||
import java.nio.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public final class IcepServer extends Server {
|
||||
|
||||
public IcepServer() {
|
||||
this(System.currentTimeMillis(), null);
|
||||
}
|
||||
|
||||
public IcepServer(long serverStartTime, final WatchFactory watch) {
|
||||
super(serverStartTime, "UDP", new IcepPrepareServlet(), watch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(AnyValue config) throws Exception {
|
||||
super.init(config);
|
||||
}
|
||||
|
||||
public void addIcepServlet(IcepServlet servlet, AnyValue conf) {
|
||||
((IcepPrepareServlet) this.prepare).addIcepServlet(servlet, conf);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Context createContext() {
|
||||
final int port = this.address.getPort();
|
||||
AtomicLong createBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("ICEP_" + port + ".Buffer.creatCounter");
|
||||
AtomicLong cycleBufferCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("ICEP_" + port + ".Buffer.cycleCounter");
|
||||
int rcapacity = Math.max(this.capacity, 8 * 1024);
|
||||
ObjectPool<ByteBuffer> bufferPool = new ObjectPool<>(createBufferCounter, cycleBufferCounter, this.bufferPoolSize,
|
||||
(Object... params) -> ByteBuffer.allocateDirect(rcapacity), null, (e) -> {
|
||||
if (e == null || e.isReadOnly() || e.capacity() != rcapacity) return false;
|
||||
e.clear();
|
||||
return true;
|
||||
});
|
||||
AtomicLong createResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("ICEP_" + port + ".Response.creatCounter");
|
||||
AtomicLong cycleResponseCounter = watch == null ? new AtomicLong() : watch.createWatchNumber("ICEP_" + port + ".Response.cycleCounter");
|
||||
ObjectPool<Response> responsePool = IcepResponse.createPool(createResponseCounter, cycleResponseCounter, this.responsePoolSize, null);
|
||||
IcepContext localcontext = new IcepContext(this.serverStartTime, this.logger, executor, bufferPool, responsePool,
|
||||
this.maxbody, this.charset, this.address, this.prepare, this.watch, this.readTimeoutSecond, this.writeTimeoutSecond);
|
||||
responsePool.setCreator((Object... params) -> new IcepResponse(localcontext, new IcepRequest(localcontext)));
|
||||
return localcontext;
|
||||
}
|
||||
}
|
||||
30
src-plugin/com/wentch/redkale/net/icep/IcepServlet.java
Normal file
30
src-plugin/com/wentch/redkale/net/icep/IcepServlet.java
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class IcepServlet implements Servlet<IcepRequest, IcepResponse> {
|
||||
|
||||
AnyValue conf; //当前Servlet的配置
|
||||
|
||||
public abstract short getRequestid();
|
||||
|
||||
@Override
|
||||
public final boolean equals(Object obj) {
|
||||
return obj != null && obj.getClass() == this.getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int hashCode() {
|
||||
return this.getClass().hashCode();
|
||||
}
|
||||
}
|
||||
72
src-plugin/com/wentch/redkale/net/icep/NodeIcepServer.java
Normal file
72
src-plugin/com/wentch/redkale/net/icep/NodeIcepServer.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep;
|
||||
|
||||
import com.wentch.redkale.boot.*;
|
||||
import com.wentch.redkale.boot.ClassFilter.FilterEntry;
|
||||
import com.wentch.redkale.net.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import com.wentch.redkale.util.AnyValue.DefaultAnyValue;
|
||||
import java.lang.reflect.*;
|
||||
import java.net.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
@NodeProtocol({"ICEP"})
|
||||
public class NodeIcepServer extends NodeServer {
|
||||
|
||||
private final IcepServer icepServer;
|
||||
|
||||
public NodeIcepServer(Application application, AnyValue serconf) {
|
||||
super(application, application.getResourceFactory().createChild(), createServer(application, serconf));
|
||||
this.icepServer = (IcepServer) server;
|
||||
}
|
||||
|
||||
private static Server createServer(Application application, AnyValue serconf) {
|
||||
return new IcepServer(application.getStartTime(), application.getWatchFactory());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getSocketAddress() {
|
||||
return icepServer == null ? null : icepServer.getSocketAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ClassFilter<Servlet> createServletClassFilter() {
|
||||
return createClassFilter(null, null, IcepServlet.class, null, "servlets", "servlet");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadServlet(ClassFilter<? extends Servlet> servletFilter) throws Exception {
|
||||
if (icepServer != null) loadIcepServlet(this.nodeConf.getAnyValue("servlets"), servletFilter);
|
||||
}
|
||||
|
||||
protected void loadIcepServlet(final AnyValue conf, ClassFilter<? extends Servlet> filter) throws Exception {
|
||||
final StringBuilder sb = logger.isLoggable(Level.FINE) ? new StringBuilder() : null;
|
||||
final String threadName = "[" + Thread.currentThread().getName() + "] ";
|
||||
for (FilterEntry<? extends Servlet> en : filter.getFilterEntrys()) {
|
||||
Class<IcepServlet> clazz = (Class<IcepServlet>) en.getType();
|
||||
if (Modifier.isAbstract(clazz.getModifiers())) continue;
|
||||
final IcepServlet servlet = clazz.newInstance();
|
||||
factory.inject(servlet);
|
||||
DefaultAnyValue servletConf = (DefaultAnyValue) en.getProperty();
|
||||
this.icepServer.addIcepServlet(servlet, servletConf);
|
||||
if (sb != null) sb.append(threadName).append(" Loaded ").append(clazz.getName()).append(" --> ").append(format(servlet.getRequestid())).append(LINE_SEPARATOR);
|
||||
}
|
||||
if (sb != null && sb.length() > 0) logger.log(Level.FINE, sb.toString());
|
||||
}
|
||||
|
||||
private static String format(short value) {
|
||||
String str = Integer.toHexString(value);
|
||||
if (str.length() == 1) return "0x000" + str;
|
||||
if (str.length() == 2) return "0x00" + str;
|
||||
if (str.length() == 3) return "0x0" + str;
|
||||
return "0x" + str;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.attr;
|
||||
|
||||
import com.wentch.redkale.net.icep.*;
|
||||
import com.wentch.redkale.net.icep.stun.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class MappedAddressAttribute extends StunAttribute {
|
||||
|
||||
protected InetSocketAddress address;
|
||||
|
||||
public MappedAddressAttribute() {
|
||||
}
|
||||
|
||||
public MappedAddressAttribute(InetSocketAddress address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
/**
|
||||
0000 b8 2a 72 c8 7c 7e 08 57 00 60 3b f6 08 00 45 00 .*r.|~.W.`;...E.
|
||||
0010 00 78 00 00 40 00 30 11 6f 1a d8 5d f6 12 0a 1c .x..@.0.o..]....
|
||||
0020 02 cf 0d 96 e2 ba 00 64 f0 b3
|
||||
01 01 00 48 21 12 a4 42 50 38 69 55 2b 65 78 66 4e 68 76 32 00 01 .BP8iU+exfNhv2..
|
||||
0040 00 08 00 01 e2 ba 71 5c fb ed 00 04 00 08 00 01 ......q\........
|
||||
0050 0d 96 d8 5d f6 12 00 05 00 08 00 01 0d 97 d8 5d ...]...........]
|
||||
0060 f6 0f 80 20 00 08 00 01 c3 a8 50 4e 5f af 80 22 ... ......PN_.."
|
||||
0070 00 14 56 6f 76 69 64 61 2e 6f 72 67 20 30 2e 39 ..Vovida.org 0.9
|
||||
0080 37 2d 43 50 43 00 7-CPC.
|
||||
@param args
|
||||
@throws Exception
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
final byte[] transactionid = format("21 12 a4 42 50 38 69 55 2b 65 78 66 4e 68 76 32");
|
||||
byte[] attrs = format("80 20 00 08 00 01 c3 a8 50 4e 5f af");
|
||||
XorMappedAddressAttribute attr = new XorMappedAddressAttribute().decode(ByteBuffer.wrap(attrs), transactionid);
|
||||
System.out.println(attr);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
attr.encode(buffer, transactionid);
|
||||
buffer.flip();
|
||||
Utility.println(null, buffer);
|
||||
}
|
||||
|
||||
protected static byte[] format(String string) {
|
||||
String[] strs = string.split("\\s+");
|
||||
byte[] bs = new byte[strs.length];
|
||||
for (int i = 0; i < bs.length; i++) {
|
||||
bs[i] = (byte) Integer.parseInt(strs[i], 16);
|
||||
}
|
||||
return bs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedAddressAttribute decode(final ByteBuffer buffer, final byte[] transactionid) {
|
||||
final short attrid = (short) (buffer.getShort() & 0x00ff);
|
||||
if (attrid != getAttributeid()) throw new IcepException(this.getClass().getSimpleName() + " has illegal attributeid " + attrid);
|
||||
final int bodysize = buffer.getShort() & 0xffff;
|
||||
final short family = buffer.getShort();
|
||||
if (family == 0x0001 && bodysize != 8) throw new IcepException("family = " + family + " but bodysize = " + bodysize);
|
||||
if (family == 0x0002 && bodysize != 20) throw new IcepException("family = " + family + " but bodysize = " + bodysize);
|
||||
final int port = buffer.getShort() & 0xffff;
|
||||
byte[] bytes = new byte[family == 0x0002 ? 16 : 4];
|
||||
buffer.get(bytes);
|
||||
try {
|
||||
this.address = new InetSocketAddress(InetAddress.getByAddress(bytes), port);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IcepException("port = " + port + " and address = " + Utility.binToHexString(bytes), e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer encode(final ByteBuffer buffer, final byte[] transactionid) {
|
||||
final boolean ipv6 = this.address.getAddress() instanceof Inet6Address;
|
||||
buffer.putShort((short) getAttributeid());
|
||||
buffer.putShort((short) (ipv6 ? 20 : 8));
|
||||
buffer.putShort((short) (ipv6 ? 0x0002 : 0x0001));
|
||||
buffer.putShort((short) this.address.getPort());
|
||||
final byte[] bytes = this.address.getAddress().getAddress();
|
||||
buffer.put(bytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getAttributeid() {
|
||||
return 0x0001;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MAPPED-ADDRESS";
|
||||
}
|
||||
|
||||
public InetSocketAddress getInetSocketAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName() + ":" + address;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.attr;
|
||||
|
||||
import com.wentch.redkale.net.icep.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class XorMappedAddressAttribute extends MappedAddressAttribute {
|
||||
|
||||
public XorMappedAddressAttribute() {
|
||||
}
|
||||
|
||||
public XorMappedAddressAttribute(InetSocketAddress address) {
|
||||
super(address);
|
||||
}
|
||||
|
||||
/**
|
||||
0000 b8 2a 72 c8 7c 7e 08 57 00 60 3b f6 08 00 45 00 .*r.|~.W.`;...E.
|
||||
0010 00 78 00 00 40 00 30 11 6f 1a d8 5d f6 12 0a 1c .x..@.0.o..]....
|
||||
0020 02 cf 0d 96 e2 ba 00 64 f0 b3
|
||||
01 01 00 48 21 12 a4 42 50 38 69 55 2b 65 78 66 4e 68 76 32 00 01 .BP8iU+exfNhv2..
|
||||
0040 00 08 00 01 e2 ba 71 5c fb ed 00 04 00 08 00 01 ......q\........
|
||||
0050 0d 96 d8 5d f6 12 00 05 00 08 00 01 0d 97 d8 5d ...]...........]
|
||||
0060 f6 0f 80 20 00 08 00 01 c3 a8 50 4e 5f af 80 22 ... ......PN_.."
|
||||
0070 00 14 56 6f 76 69 64 61 2e 6f 72 67 20 30 2e 39 ..Vovida.org 0.9
|
||||
0080 37 2d 43 50 43 00 7-CPC.
|
||||
@param args
|
||||
@throws Exception
|
||||
*/
|
||||
public static void main(String[] args) throws Exception {
|
||||
final byte[] transactionid = format("21 12 a4 42 50 38 69 55 2b 65 78 66 4e 68 76 32");
|
||||
byte[] attrs = format("80 20 00 08 00 01 c3 a8 50 4e 5f af");
|
||||
XorMappedAddressAttribute attr = new XorMappedAddressAttribute().decode(ByteBuffer.wrap(attrs), transactionid);
|
||||
System.out.println(attr);
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1024);
|
||||
attr.encode(buffer, transactionid);
|
||||
buffer.flip();
|
||||
Utility.println(null, buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XorMappedAddressAttribute decode(final ByteBuffer buffer, final byte[] transactionid) {
|
||||
final short attrid = (short) (buffer.getShort() & 0x00ff);
|
||||
if (attrid != getAttributeid()) throw new IcepException(this.getClass().getSimpleName() + " has illegal attributeid " + attrid);
|
||||
final int bodysize = buffer.getShort() & 0xffff;
|
||||
final short family = buffer.getShort();
|
||||
if (family == 0x0001 && bodysize != 8) throw new IcepException("family = " + family + " but bodysize = " + bodysize);
|
||||
if (family == 0x0002 && bodysize != 20) throw new IcepException("family = " + family + " but bodysize = " + bodysize);
|
||||
final int port = (buffer.getShort() ^ ((transactionid[0] << 8 & 0x0000FF00) | (transactionid[1] & 0x000000FF))) & 0xffff;
|
||||
byte[] bytes = new byte[family == 0x0002 ? 16 : 4];
|
||||
buffer.get(bytes);
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] ^= transactionid[i];
|
||||
}
|
||||
try {
|
||||
this.address = new InetSocketAddress(InetAddress.getByAddress(bytes), port);
|
||||
} catch (UnknownHostException e) {
|
||||
throw new IcepException("port = " + port + " and address = " + Utility.binToHexString(bytes), e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer encode(final ByteBuffer buffer, final byte[] transactionid) {
|
||||
final boolean ipv6 = this.address.getAddress() instanceof Inet6Address;
|
||||
buffer.putShort((short) getAttributeid());
|
||||
buffer.putShort((short) (ipv6 ? 20 : 8));
|
||||
buffer.putShort((short) (ipv6 ? 0x0002 : 0x0001));
|
||||
buffer.putShort((short) (this.address.getPort() ^ ((transactionid[0] << 8 & 0x0000FF00) | (transactionid[1] & 0x000000FF))));
|
||||
final byte[] bytes = this.address.getAddress().getAddress();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] ^= transactionid[i];
|
||||
}
|
||||
buffer.put(bytes);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getAttributeid() {
|
||||
return 0x0020;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "XOR-MAPPED-ADDRESS";
|
||||
}
|
||||
|
||||
}
|
||||
203
src-plugin/com/wentch/redkale/net/icep/rtp/RtpHeader.java
Normal file
203
src-plugin/com/wentch/redkale/net/icep/rtp/RtpHeader.java
Normal file
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.rtp;
|
||||
|
||||
import com.wentch.redkale.net.icep.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
* The RTP header has the following format:
|
||||
*
|
||||
* 0 1 2 3
|
||||
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* |V=2|P|X| CC |M| PT | sequence number |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | timestamp |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
* | synchronization source (SSRC) identifier |
|
||||
* +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|
||||
* | contributing source (CSRC) identifiers |
|
||||
* | |
|
||||
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
*
|
||||
* The first twelve octets are present in every RTP packet, while the
|
||||
* list of CSRC identifiers is present only when inserted by a mixer.
|
||||
*
|
||||
* The version defined by RFC3550 specification is two.
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class RtpHeader implements IcepCoder<RtpHeader> {
|
||||
|
||||
/**
|
||||
* 该值占4个字节, 由7种数据组成,按位存储
|
||||
* version(2bits) + padding(1bit) + extend(1bit) + crscs(4bits) + marker(1bit) + payloadtype(7bits) + sn(16bits)
|
||||
* version : 占2比特。 表示RTP 的版本号 0b10。
|
||||
* padding : 占1比特。 置“1”表示用户数据最后加有填充位,用户数据中最后一个字节是填充位计数,它表示一共加了多少个填充位。
|
||||
* 在两种情况下可能需要填充: 一是某些加密算法要求数据块大小固定;二是在一个低层协议数据包中装载多个RTP 分组。
|
||||
* extend : 占1比特。 置“1”表示RTP 报头后紧随一个扩展报头。
|
||||
* crscs : 占4比特。 CSRC计数包括紧接在固定头后CSRC标识符个数。
|
||||
* marker : 占1比特。 标记解释由设置定义,目的在于允许重要事件在包流中标记出来。设置可定义其他标示位,或通过改变位数量来指定没有标记位。
|
||||
* payloadtype : 占7比特。 载荷类型: 记录后面资料使用哪种 Codec , receiver 端找出相应的 decoder 解码出來。
|
||||
* sn : 占16比特。 每发送一个RTP数据包该序号增加1。该序号在接收方可用来发现丢失的数据包和对到来的数据包进行排序。 初值为随机数,每发送一个增加1。可供接收方检测分组丢失和恢复分组次序。
|
||||
*
|
||||
*/
|
||||
private int headval = 0b10_0_0_0000_0_0000000_0000000000000000;
|
||||
|
||||
/**
|
||||
* 占4个字节。 时间戳 表示RTP分组第一个字节的取样时刻。其初值为随机数,每个采用周期加1。如果每次传送20ms的数据,由于音频的采样频率为8000Hz,即每20ms有160次采样,则每传送20ms的数据,时戳增加160。
|
||||
*/
|
||||
private int timestamp;
|
||||
|
||||
/**
|
||||
* 同步源标识符(SSRC) 相当于每个数据流的唯一ID
|
||||
* 占4字节。 用来标识一个同步源。此标识符是随机选择的,但要保证同一RTP会话中的任意两个SSRC各不相同,RTP必须检测并解决冲突。
|
||||
*/
|
||||
private int ssrc;
|
||||
|
||||
/**
|
||||
* 提供源标识符(CSRC)
|
||||
* 占0-60个字节。 它可有0~15项标识符,每一项长度固定为32比特,其项数由CC字段(crscs)来确定。如果提供源多于15个,则只有15个被标识。
|
||||
*/
|
||||
private int[] csrc;
|
||||
|
||||
/**
|
||||
* 占2字节
|
||||
*/
|
||||
private short exttype;
|
||||
|
||||
/**
|
||||
* 扩展数据
|
||||
* 占4*N字节, 必须是4字节的倍数
|
||||
*/
|
||||
private byte[] extdata;
|
||||
|
||||
@Override
|
||||
public RtpHeader decode(final ByteBuffer buffer) {
|
||||
this.headval = buffer.getInt();
|
||||
this.timestamp = buffer.getInt();
|
||||
this.ssrc = buffer.getInt();
|
||||
final int csrcs = getCrscs();
|
||||
if (csrcs > 0) {
|
||||
this.csrc = new int[csrcs];
|
||||
for (int i = 0; i < csrcs; i++) {
|
||||
this.csrc[i] = buffer.getInt();
|
||||
}
|
||||
}
|
||||
if (isExtend()) {
|
||||
this.exttype = buffer.getShort();
|
||||
int extdatalen = (buffer.getShort() & 0xffff) * 4;
|
||||
this.extdata = new byte[extdatalen];
|
||||
buffer.get(extdata);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer encode(final ByteBuffer buffer) {
|
||||
buffer.putInt(this.headval);
|
||||
buffer.putInt(this.timestamp);
|
||||
buffer.putInt(this.ssrc);
|
||||
final int csrcs = getCrscs();
|
||||
if (csrcs > 0) {
|
||||
for (int i = 0; i < csrcs; i++) {
|
||||
buffer.putInt(this.csrc[i]);
|
||||
}
|
||||
}
|
||||
if (isExtend()) {
|
||||
buffer.putShort(this.exttype);
|
||||
buffer.putShort((short) (this.extdata.length / 4));
|
||||
buffer.put(this.extdata);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static int getSsrc(final ByteBuffer buffer) {
|
||||
return buffer.getInt(8);
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return (headval >> 30) & 0b11;
|
||||
}
|
||||
|
||||
public boolean isPadding() {
|
||||
return (headval & 0b00_1_0_0000_0_0000000_0000000000000000) > 0;
|
||||
}
|
||||
|
||||
public void setPadding(boolean padding) {
|
||||
if (padding) {
|
||||
headval |= 0b00_1_0_0000_0_0000000_0000000000000000;
|
||||
} else {
|
||||
headval &= 0b11_0_1_1111_1_1111111_1111111111111111;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isExtend() {
|
||||
return (headval & 0b00_0_1_0000_0_0000000_0000000000000000) > 0;
|
||||
}
|
||||
|
||||
public short getExttype() {
|
||||
return exttype;
|
||||
}
|
||||
|
||||
public void setExttype(short exttype) {
|
||||
this.exttype = exttype;
|
||||
}
|
||||
|
||||
public int[] getCsrc() {
|
||||
return csrc;
|
||||
}
|
||||
|
||||
public void setCsrc(int[] csrc) {
|
||||
this.csrc = csrc != null && csrc.length > 0 ? csrc : null;
|
||||
if (this.csrc != null) {
|
||||
this.headval = (headval & 0b11_1_1_0000_1_1111111_1111111111111111) | ((csrc.length << 24) & 0b00_0_0_1111_0_0000000_0000000000000000);
|
||||
} else {
|
||||
this.headval &= 0b11_1_1_0000_1_1111111_1111111111111111;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] getExtdata() {
|
||||
return extdata;
|
||||
}
|
||||
|
||||
public void setExtdata(byte[] exdata) {
|
||||
boolean extend = exdata != null || exdata.length > 0;
|
||||
if (extend) {
|
||||
if ((exdata.length & 0b100) > 0) throw new RuntimeException("extdata length(" + exdata.length + ") is illegal");
|
||||
headval |= 0b00_0_1_0000_0_0000000_0000000000000000;
|
||||
} else {
|
||||
headval &= 0b11_1_0_1111_1_1111111_1111111111111111;
|
||||
}
|
||||
this.extdata = (exdata != null && exdata.length > 0) ? exdata : null;
|
||||
}
|
||||
|
||||
public int getCrscs() {
|
||||
return (headval >> 24) & 0b1111;
|
||||
}
|
||||
|
||||
public boolean isMarker() {
|
||||
return (headval & 0b00_0_0_0000_1_0000000_0000000000000000) > 0;
|
||||
}
|
||||
|
||||
public int getPayloadtype() {
|
||||
return (headval >> 16) & 0b1111111;
|
||||
}
|
||||
|
||||
public void setPayloadtype(int payloadtype) {
|
||||
headval = (headval & 0b11_1_1_1111_1_0000000_1111111111111111) | ((payloadtype << 16) & 0b00_0_0_0000_0_1111111_0000000000000000);
|
||||
}
|
||||
|
||||
public int getSeqnumber() {
|
||||
return headval & 0xFFFF;
|
||||
}
|
||||
|
||||
public void setSeqnumber(int seqnumber) {
|
||||
headval = (headval >> 16 << 16) | (seqnumber & 0x0000FFFF);
|
||||
}
|
||||
|
||||
}
|
||||
36
src-plugin/com/wentch/redkale/net/icep/rtp/RtpPacket.java
Normal file
36
src-plugin/com/wentch/redkale/net/icep/rtp/RtpPacket.java
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.rtp;
|
||||
|
||||
import com.wentch.redkale.net.icep.*;
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class RtpPacket implements IcepCoder<RtpPacket> {
|
||||
|
||||
private RtpHeader header;
|
||||
|
||||
private byte[] payload;
|
||||
|
||||
@Override
|
||||
public RtpPacket decode(final ByteBuffer buffer) {
|
||||
if (header == null) this.header = new RtpHeader();
|
||||
this.header.decode(buffer);
|
||||
this.payload = new byte[buffer.remaining()];
|
||||
buffer.get(payload);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer encode(final ByteBuffer buffer) {
|
||||
this.header.encode(buffer).put(payload);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.stun;
|
||||
|
||||
import java.nio.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public abstract class StunAttribute {
|
||||
|
||||
public abstract StunAttribute decode(final ByteBuffer buffer, final byte[] transactionid);
|
||||
|
||||
public abstract ByteBuffer encode(final ByteBuffer buffer, final byte[] transactionid);
|
||||
|
||||
public abstract short getAttributeid();
|
||||
|
||||
public abstract String getName();
|
||||
}
|
||||
127
src-plugin/com/wentch/redkale/net/icep/stun/StunHeader.java
Normal file
127
src-plugin/com/wentch/redkale/net/icep/stun/StunHeader.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.stun;
|
||||
|
||||
import com.wentch.redkale.net.icep.*;
|
||||
import com.wentch.redkale.util.*;
|
||||
import java.nio.*;
|
||||
import java.security.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class StunHeader implements IcepCoder<StunHeader> {
|
||||
|
||||
public static final int MAGIC_COOKIE = 0x2112A442;
|
||||
|
||||
//---------------------------- type -------------------------
|
||||
private static final int TYPE_BIT_MASK = 0x0110;
|
||||
|
||||
public static final short TYPE_REQUEST = 0x0000;
|
||||
|
||||
public static final short TYPE_INDICATION = 0x0010;
|
||||
|
||||
public static final short TYPE_SUCCESS = 0x0100;
|
||||
|
||||
public static final short TYPE_ERROR = 0x0110;
|
||||
|
||||
//---------------------------- action -------------------------
|
||||
public static final int ACTION_BIT_MASK = 0xCEEF;
|
||||
|
||||
public static final short ACTION_BINDING = 0x0001;
|
||||
|
||||
public static final short ACTION_ALLOCATE = 0x0003;
|
||||
|
||||
public static final short ACTION_REFRESH = 0x0004;
|
||||
|
||||
public static final short ACTION_SEND = 0x0006;
|
||||
|
||||
public static final short ACTION_DATA = 0x0007;
|
||||
|
||||
public static final short ACTION_CREATE_PERMISSION = 0x0008;
|
||||
|
||||
public static final short ACTION_CHANNELBIND = 0x0009;
|
||||
|
||||
//-----------------------------------------------------------
|
||||
private short requestid; //无符号 2bytes 首位2bits必定是00, 所以该值不会为负数
|
||||
|
||||
private char bodysize; //无符号 2bytes
|
||||
|
||||
private byte[] transactionid; //RFC5389 =MAGIC_COOKIE + byte[12] = byte[16];
|
||||
|
||||
public static byte[] generateTransactionid() {
|
||||
byte[] transactions = new byte[16];
|
||||
// Get a secure PRNG
|
||||
SecureRandom random = new SecureRandom();
|
||||
random.nextBytes(transactions);
|
||||
transactions[0] = 0x21;
|
||||
transactions[1] = 0x12;
|
||||
transactions[2] = (byte) 0xA4;
|
||||
transactions[3] = 0x42;
|
||||
return transactions;
|
||||
}
|
||||
|
||||
public StunHeader(short requestid, byte[] transactionid0) {
|
||||
this.requestid = requestid;
|
||||
this.transactionid = transactionid0 == null ? generateTransactionid() : transactionid0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StunHeader decode(final ByteBuffer buffer) {
|
||||
this.requestid = buffer.getShort();
|
||||
this.bodysize = buffer.getChar();
|
||||
this.transactionid = new byte[16];
|
||||
buffer.get(transactionid);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteBuffer encode(final ByteBuffer buffer) {
|
||||
buffer.putShort(this.requestid);
|
||||
buffer.putChar(this.bodysize);
|
||||
buffer.put(transactionid);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.getClass().getSimpleName() + "[requestid = " + format(requestid) + ", bodysize = " + (int) (bodysize) + ", transactionid = " + Utility.binToHexString(transactionid) + "]";
|
||||
}
|
||||
|
||||
private static String format(short value) {
|
||||
String str = Integer.toHexString(value);
|
||||
if (str.length() == 1) return "0x000" + str;
|
||||
if (str.length() == 2) return "0x00" + str;
|
||||
if (str.length() == 3) return "0x0" + str;
|
||||
return "0x" + str;
|
||||
}
|
||||
|
||||
public void setRequestid(int requestid) {
|
||||
this.requestid = (short) requestid;
|
||||
}
|
||||
|
||||
public void setRequestid(short requestid) {
|
||||
this.requestid = requestid;
|
||||
}
|
||||
|
||||
public void setBodysize(char bodysize) {
|
||||
this.bodysize = bodysize;
|
||||
}
|
||||
|
||||
public void setBodysize(int bodysize) {
|
||||
this.bodysize = (char) bodysize;
|
||||
}
|
||||
|
||||
public int getBodysize() {
|
||||
return bodysize;
|
||||
}
|
||||
|
||||
public byte[] getTransactionid() {
|
||||
return transactionid;
|
||||
}
|
||||
|
||||
}
|
||||
49
src-plugin/com/wentch/redkale/net/icep/stun/StunPacket.java
Normal file
49
src-plugin/com/wentch/redkale/net/icep/stun/StunPacket.java
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* To change this license header, choose License Headers in Project Properties.
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package com.wentch.redkale.net.icep.stun;
|
||||
|
||||
import java.nio.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author zhangjx
|
||||
*/
|
||||
public class StunPacket {
|
||||
|
||||
private StunHeader header;
|
||||
|
||||
private final List<StunAttribute> attributes = new ArrayList<>();
|
||||
|
||||
public StunPacket(StunHeader header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
public ByteBuffer encode(final ByteBuffer buffer) {
|
||||
int start = buffer.position();
|
||||
header.encode(buffer);
|
||||
final byte[] transactionid = header.getTransactionid();
|
||||
for (StunAttribute attr : attributes) {
|
||||
attr.encode(buffer, transactionid);
|
||||
}
|
||||
int end = buffer.position();
|
||||
buffer.putShort(start + 2, (short) (end - start - 20));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public void addAttribute(StunAttribute attribute) {
|
||||
this.attributes.add(attribute);
|
||||
}
|
||||
|
||||
public StunHeader getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public List<StunAttribute> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user