0) {
137 | // OR the bit into place, so the other bits remain
138 | // undisturbed.
139 | out_value |= out_value_mask;
140 | }
141 |
142 | // move to the next bit of input
143 | in_pos++;
144 |
145 | // get ready for the next bit of output
146 | // shift with zero extension
147 | out_value_mask = (short) (out_value_mask >>> 1);
148 | }
149 | return out_value;
150 | }
151 |
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/src/main/java/com/phono/srtplight/Log.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 Voxeo Corp.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.phono.srtplight;
18 |
19 | /**
20 | * A simple logger.
21 | */
22 | public class Log {
23 |
24 | /**
25 | * Log all text
26 | */
27 | final public static int ALL = 9;
28 | /**
29 | * Log verbose text (and down)
30 | */
31 | final public static int VERB = 5;
32 | /**
33 | * Log debug text (and down)
34 | */
35 | final public static int DEBUG = 4;
36 | /**
37 | * Log info text (and down)
38 | */
39 | final public static int INFO = 3;
40 | /**
41 | * Log warning text (and down)
42 | */
43 | final public static int WARN = 2;
44 | /**
45 | * Log error text (and down)
46 | */
47 | final public static int ERROR = 1;
48 | /**
49 | * Log nothing
50 | */
51 | public static int NONE = 0;
52 | private static int _level = 1;
53 | private static LogFace __logger;
54 | private static String[] levels = {"NONE","ERROR","WARN","INFO","DEBUG","VERB"};
55 |
56 | private static LogFace mkDefaultLogger() {
57 | return new LogFace() {
58 |
59 | public void e(String message) {
60 | System.out.println(message);
61 | }
62 |
63 | public void d(String message) {
64 | System.out.println(message);
65 | }
66 |
67 | public void w(String message) {
68 | System.out.println(message);
69 | }
70 |
71 | public void v(String message) {
72 | System.out.println(message);
73 | }
74 |
75 | public void i(String message) {
76 | System.out.println(message);
77 | }
78 | };
79 | }
80 |
81 |
82 |
83 | /**
84 | * Constructor for the Log object
85 | * never actually used
86 | */
87 | private Log() {
88 | }
89 |
90 | /**
91 | * Sets the level attribute of the Log class
92 | *
93 | * @param level The new level value
94 | */
95 | public static void setLevel(int level) {
96 | _level = level;
97 | }
98 |
99 | /**
100 | * Sets the LogFace implementation for this platform the Log class
101 | * if not set we default to printing to std out
102 | *
103 | * @param level The new level value
104 | */
105 | public static void setLogger(LogFace logger) {
106 | __logger = logger;
107 | }
108 |
109 | /**
110 | * Gets the level attribute of the Log class
111 | *
112 | * @return The level value
113 | */
114 | public static int getLevel() {
115 | return _level;
116 | }
117 |
118 | /**
119 | * error
120 | *
121 | * @param string String
122 | */
123 | public static void error(String string) {
124 | if (_level >= ERROR) {
125 | log(ERROR, string);
126 | }
127 | }
128 |
129 | /**
130 | * warn
131 | *
132 | * @param string String
133 | */
134 | public static void warn(String string) {
135 | if (_level >= WARN) {
136 | log(WARN, string);
137 | }
138 | }
139 |
140 | /**
141 | * info
142 | *
143 | * @param string String
144 | */
145 | public static void info(String string) {
146 | if (_level >= INFO) {
147 | log(INFO, string);
148 | }
149 | }
150 |
151 | /**
152 | * debug
153 | *
154 | * @param string Description of Parameter
155 | */
156 | public static void debug(String string) {
157 | if (_level >= DEBUG) {
158 | log(DEBUG, string);
159 | }
160 | }
161 |
162 | /**
163 | * verbose
164 | *
165 | * @param string Description of Parameter
166 | */
167 | public static void verb(String string) {
168 | if (_level >= VERB) {
169 | log(VERB, string);
170 | }
171 | }
172 |
173 | /**
174 | * where
175 | */
176 | public static void where() {
177 | Exception x = new Exception("Called From");
178 | x.printStackTrace();
179 | }
180 |
181 | private static void log(int level, String string) {
182 | if (__logger == null) {
183 | __logger = mkDefaultLogger();
184 | }
185 | String message = (((level>0) && (level <= VERB))?levels[level]:"") +": "
186 | + System.currentTimeMillis() + " "
187 | + Thread.currentThread().getName() + "->" + string;
188 | switch (level) {
189 | case ERROR: {
190 | __logger.e(message);
191 | break;
192 | }
193 | case DEBUG: {
194 | __logger.d(message);
195 | break;
196 | }
197 | case WARN: {
198 | __logger.w(message);
199 | break;
200 | }
201 | case VERB: {
202 | __logger.v(message);
203 | break;
204 | }
205 | case INFO: {
206 | __logger.i(message);
207 | break;
208 | }
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/main/java/biz/source_code/Base64Coder.java:
--------------------------------------------------------------------------------
1 | // Copyright 2003-2010 Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
2 | // www.source-code.biz, www.inventec.ch/chdh
3 | //
4 | // This module is multi-licensed and may be used under the terms
5 | // of any of the following licenses:
6 | //
7 | // EPL, Eclipse Public License, V1.0 or later, http://www.eclipse.org/legal
8 | // LGPL, GNU Lesser General Public License, V2.1 or later, http://www.gnu.org/licenses/lgpl.html
9 | // GPL, GNU General Public License, V2 or later, http://www.gnu.org/licenses/gpl.html
10 | // AL, Apache License, V2.0 or later, http://www.apache.org/licenses
11 | // BSD, BSD License, http://www.opensource.org/licenses/bsd-license.php
12 | //
13 | // Please contact the author if you need another license.
14 | // This module is provided "as is", without warranties of any kind.
15 |
16 | package biz.source_code;
17 |
18 | /**
19 | * A Base64 encoder/decoder.
20 | *
21 | *
22 | * This class is used to encode and decode data in Base64 format as described in RFC 1521.
23 | *
24 | *
25 | * Project home page: www.source-code.biz/base64coder/java
26 | * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland
27 | * Multi-licensed: EPL / LGPL / GPL / AL / BSD.
28 | */
29 | public class Base64Coder {
30 |
31 | // The line separator string of the operating system.
32 | private static final String systemLineSeparator = System.getProperty("line.separator");
33 |
34 | // Mapping table from 6-bit nibbles to Base64 characters.
35 | private static char[] map1 = new char[64];
36 | static {
37 | int i=0;
38 | for (char c='A'; c<='Z'; c++) map1[i++] = c;
39 | for (char c='a'; c<='z'; c++) map1[i++] = c;
40 | for (char c='0'; c<='9'; c++) map1[i++] = c;
41 | map1[i++] = '+'; map1[i++] = '/'; }
42 |
43 | // Mapping table from Base64 characters to 6-bit nibbles.
44 | private static byte[] map2 = new byte[128];
45 | static {
46 | for (int i=0; isun.misc.BASE64Encoder.encodeBuffer(byte[]).
61 | * @param in An array containing the data bytes to be encoded.
62 | * @return A String containing the Base64 encoded data, broken into lines.
63 | */
64 | public static String encodeLines (byte[] in) {
65 | return encodeLines(in, 0, in.length, 76, systemLineSeparator); }
66 |
67 | /**
68 | * Encodes a byte array into Base 64 format and breaks the output into lines.
69 | * @param in An array containing the data bytes to be encoded.
70 | * @param iOff Offset of the first byte in in to be processed.
71 | * @param iLen Number of bytes to be processed in in, starting at iOff.
72 | * @param lineLen Line length for the output data. Should be a multiple of 4.
73 | * @param lineSeparator The line separator to be used to separate the output lines.
74 | * @return A String containing the Base64 encoded data, broken into lines.
75 | */
76 | public static String encodeLines (byte[] in, int iOff, int iLen, int lineLen, String lineSeparator) {
77 | int blockLen = (lineLen*3) / 4;
78 | if (blockLen <= 0) throw new IllegalArgumentException();
79 | int lines = (iLen+blockLen-1) / blockLen;
80 | int bufLen = ((iLen+2)/3)*4 + lines*lineSeparator.length();
81 | StringBuilder buf = new StringBuilder(bufLen);
82 | int ip = 0;
83 | while (ip < iLen) {
84 | int l = Math.min(iLen-ip, blockLen);
85 | buf.append (encode(in, iOff+ip, l));
86 | buf.append (lineSeparator);
87 | ip += l; }
88 | return buf.toString(); }
89 |
90 | /**
91 | * Encodes a byte array into Base64 format.
92 | * No blanks or line breaks are inserted in the output.
93 | * @param in An array containing the data bytes to be encoded.
94 | * @return A character array containing the Base64 encoded data.
95 | */
96 | public static char[] encode (byte[] in) {
97 | return encode(in, 0, in.length); }
98 |
99 | /**
100 | * Encodes a byte array into Base64 format.
101 | * No blanks or line breaks are inserted in the output.
102 | * @param in An array containing the data bytes to be encoded.
103 | * @param iLen Number of bytes to process in in.
104 | * @return A character array containing the Base64 encoded data.
105 | */
106 | public static char[] encode (byte[] in, int iLen) {
107 | return encode(in, 0, iLen); }
108 |
109 | /**
110 | * Encodes a byte array into Base64 format.
111 | * No blanks or line breaks are inserted in the output.
112 | * @param in An array containing the data bytes to be encoded.
113 | * @param iOff Offset of the first byte in in to be processed.
114 | * @param iLen Number of bytes to process in in, starting at iOff.
115 | * @return A character array containing the Base64 encoded data.
116 | */
117 | public static char[] encode (byte[] in, int iOff, int iLen) {
118 | int oDataLen = (iLen*4+2)/3; // output length without padding
119 | int oLen = ((iLen+2)/3)*4; // output length including padding
120 | char[] out = new char[oLen];
121 | int ip = iOff;
122 | int iEnd = iOff + iLen;
123 | int op = 0;
124 | while (ip < iEnd) {
125 | int i0 = in[ip++] & 0xff;
126 | int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
127 | int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
128 | int o0 = i0 >>> 2;
129 | int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
130 | int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
131 | int o3 = i2 & 0x3F;
132 | out[op++] = map1[o0];
133 | out[op++] = map1[o1];
134 | out[op] = op < oDataLen ? map1[o2] : '='; op++;
135 | out[op] = op < oDataLen ? map1[o3] : '='; op++; }
136 | return out; }
137 |
138 | /**
139 | * Decodes a string from Base64 format.
140 | * No blanks or line breaks are allowed within the Base64 encoded input data.
141 | * @param s A Base64 String to be decoded.
142 | * @return A String containing the decoded data.
143 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
144 | */
145 | public static String decodeString (String s) {
146 | return new String(decode(s)); }
147 |
148 | /**
149 | * Decodes a byte array from Base64 format and ignores line separators, tabs and blanks.
150 | * CR, LF, Tab and Space characters are ignored in the input data.
151 | * This method is compatible with sun.misc.BASE64Decoder.decodeBuffer(String).
152 | * @param s A Base64 String to be decoded.
153 | * @return An array containing the decoded data bytes.
154 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
155 | */
156 | public static byte[] decodeLines (String s) {
157 | char[] buf = new char[s.length()];
158 | int p = 0;
159 | for (int ip = 0; ip < s.length(); ip++) {
160 | char c = s.charAt(ip);
161 | if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
162 | buf[p++] = c; }
163 | return decode(buf, 0, p); }
164 |
165 | /**
166 | * Decodes a byte array from Base64 format.
167 | * No blanks or line breaks are allowed within the Base64 encoded input data.
168 | * @param s A Base64 String to be decoded.
169 | * @return An array containing the decoded data bytes.
170 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
171 | */
172 | public static byte[] decode (String s) {
173 | return decode(s.toCharArray()); }
174 |
175 | /**
176 | * Decodes a byte array from Base64 format.
177 | * No blanks or line breaks are allowed within the Base64 encoded input data.
178 | * @param in A character array containing the Base64 encoded data.
179 | * @return An array containing the decoded data bytes.
180 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
181 | */
182 | public static byte[] decode (char[] in) {
183 | return decode(in, 0, in.length); }
184 |
185 | /**
186 | * Decodes a byte array from Base64 format.
187 | * No blanks or line breaks are allowed within the Base64 encoded input data.
188 | * @param in A character array containing the Base64 encoded data.
189 | * @param iOff Offset of the first character in in to be processed.
190 | * @param iLen Number of characters to process in in, starting at iOff.
191 | * @return An array containing the decoded data bytes.
192 | * @throws IllegalArgumentException If the input is not valid Base64 encoded data.
193 | */
194 | public static byte[] decode (char[] in, int iOff, int iLen) {
195 | if (iLen%4 != 0) throw new IllegalArgumentException ("Length of Base64 encoded input string is not a multiple of 4.");
196 | while (iLen > 0 && in[iOff+iLen-1] == '=') iLen--;
197 | int oLen = (iLen*3) / 4;
198 | byte[] out = new byte[oLen];
199 | int ip = iOff;
200 | int iEnd = iOff + iLen;
201 | int op = 0;
202 | while (ip < iEnd) {
203 | int i0 = in[ip++];
204 | int i1 = in[ip++];
205 | int i2 = ip < iEnd ? in[ip++] : 'A';
206 | int i3 = ip < iEnd ? in[ip++] : 'A';
207 | if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
208 | throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
209 | int b0 = map2[i0];
210 | int b1 = map2[i1];
211 | int b2 = map2[i2];
212 | int b3 = map2[i3];
213 | if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
214 | throw new IllegalArgumentException ("Illegal character in Base64 encoded data.");
215 | int o0 = ( b0 <<2) | (b1>>>4);
216 | int o1 = ((b1 & 0xf)<<4) | (b2>>>2);
217 | int o2 = ((b2 & 3)<<6) | b3;
218 | out[op++] = (byte)o0;
219 | if (op+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
136 | | ~ sender info ~ |
137 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
138 | | ~ report block 1 ~ |
139 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
140 | | ~ report block 2 ~ |
141 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
142 | | ~ ... ~ |
143 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
144 | | |V=2|P| SC | PT=SDES=202 | length | |
145 | | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
146 | | | SSRC/CSRC_1 | |
147 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
148 | | ~ SDES items ~ |
149 | | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
150 | | ~ ... ~ |
151 | +>+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
152 | | |E| SRTCP index | |
153 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<+
154 | | ~ SRTCP MKI (OPTIONAL) ~ |
155 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
156 | | : authentication tag : |
157 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
158 | | |
159 | +-- Encrypted Portion Authenticated Portion -----+
160 |
161 | */
162 | void checkAuth(byte[] packet, int plen) throws RTPPacketException {
163 | if (Log.getLevel() > Log.DEBUG) {
164 | Log.verb("auth on packet " + getHex(packet, plen));
165 | }
166 | try {
167 | _scIn.deriveKeys(0);
168 |
169 | if (_doAuth) {
170 | Mac hmac = _scIn.getAuthMac();
171 |
172 | int alen = _tailIn;
173 | int offs = plen - alen;
174 | ByteBuffer m = ByteBuffer.allocate(offs);
175 | m.put(packet, 0, offs);
176 |
177 | byte[] auth = new byte[alen];
178 | System.arraycopy(packet, offs, auth, 0, alen);
179 | int mlen = plen - alen;
180 | Log.verb("mess length =" + mlen);
181 | if (Log.getLevel() > Log.DEBUG) {
182 | Log.verb("auth body " + getHex(m.array()));
183 | }
184 | ((Buffer)m).position(0);
185 | hmac.update(m);
186 | byte[] mac = hmac.doFinal();
187 |
188 | if (Log.getLevel() > Log.DEBUG) {
189 | Log.verb("auth in " + getHex(auth));
190 | }
191 | if (Log.getLevel() > Log.DEBUG) {
192 | Log.verb("auth out " + getHex(mac, alen));
193 | }
194 |
195 | for (int i = 0; i < alen; i++) {
196 | if (auth[i] != mac[i]) {
197 | throw new RTPPacketException("not authorized byte " + i + " does not match ");
198 | }
199 | }
200 | Log.verb("RTCP auth ok");
201 | }
202 | } catch (GeneralSecurityException ex) {
203 | Log.debug("RTCP auth check failed " + ex.getMessage());
204 | throw new RTPPacketException("Problem checking packet " + ex.getMessage());
205 |
206 | }
207 | }
208 |
209 | /**
210 | * calculate the outbound auth and put it at the end of the packet starting
211 | * at length - _tail space is already allocated
212 | */
213 | void appendAuth(ByteBuffer m) throws RTPPacketException, GeneralSecurityException {
214 |
215 | // strictly we might need to derive the keys here too -
216 | // since we might be doing auth but no crypt.
217 | // we don't support that so nach.
218 | Mac mac = _scOut.getAuthMac();
219 | Buffer bm = (Buffer) m;
220 | int top = bm.limit();
221 | bm.position(0);
222 | int authLoc = top - _tailOut;
223 | bm.limit(authLoc);
224 | mac.update(m);
225 | byte[] auth = mac.doFinal();
226 | bm.limit(top);
227 | bm.position(authLoc);
228 | m.put(auth,0,_tailOut);
229 | bm.position(0);
230 | if (Log.getLevel() > Log.DEBUG) {
231 | Log.verb("Authed packet " + getHex(m.array()));
232 | }
233 | }
234 |
235 | public RTCP[] inbound(DatagramPacket pkt) throws InvalidRTCPPacketException, RTPPacketException, GeneralSecurityException {
236 | ArrayList rtcps = new ArrayList();
237 | int len = pkt.getLength();
238 | byte[] data = new byte[len];
239 | System.arraycopy(pkt.getData(), 0, data, 0, len);
240 | Log.verb("RTCP packet " + SRTPProtocolImpl.getHex(data));
241 | ByteBuffer bb = ByteBuffer.wrap(data);
242 | char fh = bb.getChar();
243 | int v = (fh & ((char) (0xc000))) >>> 14;
244 | int p = (fh & ((char) (0x2000))) >>> 13;
245 | int rc = (fh & ((char) (0x1f00))) >>> 8;
246 | int pt = (char) (fh & ((char) (0x00ff)));
247 | int length = bb.getChar();
248 | int ssrc = bb.getInt();
249 |
250 | /*
251 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
252 | | |E| SRTCP index | |
253 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<+
254 | | ~ SRTCP MKI (OPTIONAL) ~ |
255 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
256 | | : authentication tag : |
257 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
258 | */
259 | int tail_len = AUTHLEN + MIKEY + INDEXLEN;
260 | int tail_offset = len - tail_len;
261 | byte[] authtag = new byte[AUTHLEN];
262 | byte[] mikey = new byte[MIKEY];
263 | Buffer bbb = (Buffer) bb;
264 | bbb.position(tail_offset);
265 | long index = bb.getInt();
266 | boolean encryption = (index < 0);
267 | index = (0x7fffffff & index);
268 | if (MIKEY > 0) {
269 | bb.get(mikey);
270 | }
271 | bb.get(authtag);
272 | Log.verb("Tail =" + tail_len + " index=" + index + " mkti=" + getHex(mikey) + " authtag=" + getHex(authtag) + " encryption=" + encryption);
273 | bbb.position(0);
274 |
275 | if (encryption) {
276 | _scIn.deriveKeys(index); // or perhaps zero ?
277 | this.checkAuth(data, len);
278 |
279 | bbb.position(0);
280 | decrypt(bb, len, tail_len, ssrc, index);
281 | bbb.position(0);
282 | while (bb.remaining() >= CLEARHEAD + tail_len) {
283 | Log.verb("RTCP packet starts at " + bbb.position());
284 | RTCP rtcp = RTCP.mkRTCP(bb);
285 | Log.verb("RTCP packet was: " + rtcp.toString());
286 | rtcps.add(rtcp);
287 | }
288 | }
289 |
290 | RTCP[] ret = new RTCP[rtcps.size()];
291 | int i = 0;
292 | for (RTCP rtcp : rtcps) {
293 | ret[i++] = rtcp;
294 | }
295 | return ret;
296 |
297 | }
298 |
299 | void decrypt(ByteBuffer pkt, int len, int tail_len, int ssrc, long index) throws GeneralSecurityException {
300 | int plen = len - tail_len - CLEARHEAD;
301 | byte[] payload = new byte[plen];
302 | Log.verb("pkt remains " + pkt.remaining() + " offset " + CLEARHEAD + " plen " + plen);
303 | for (int i = 0; i < plen; i++) {
304 | payload[i] = pkt.get(i + CLEARHEAD);
305 | }
306 | ByteBuffer in = ByteBuffer.wrap(payload);
307 | // aes likes the buffer a multiple of 32 and longer than the input.
308 | int pl = (((payload.length / 32) + 2) * 32);
309 | ByteBuffer out = ByteBuffer.allocate(pl);
310 | ByteBuffer pepper = getPepper(ssrc, index);
311 | _scIn.decipher(in, out, pepper);
312 | for (int i = 0; i < payload.length; i++) {
313 | pkt.put(i + CLEARHEAD, out.get(i));
314 | }
315 | }
316 |
317 | public static void main(String[] args) {
318 | Log.setLevel(Log.ALL);
319 | try {
320 | short[] testPacketS = {
321 | 0x81, 0xc9, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0xd4, 0x67, 0xf8, 0x33, 0x73, 0xd7, 0xc5, 0xd8,
322 | 0x63, 0x4f, 0x82, 0x74, 0x71, 0x0a, 0x1c, 0x01, 0x1f, 0xa4, 0xa9, 0x05, 0x33, 0x40, 0x2b, 0x67,
323 | 0x7b, 0x88, 0x8b, 0x4e, 0x6c, 0xfe, 0x33, 0xd2, 0xdf, 0x28, 0x02, 0xd2, 0x47, 0x6f, 0x1c, 0x28,
324 | 0x1a, 0x25, 0xc4, 0xa4, 0xf5, 0x06, 0x26, 0x9f, 0x79, 0xd7, 0x7b, 0x94, 0x77, 0xd6, 0x48, 0x30,
325 | 0xcb, 0x31, 0xd7, 0x7a, 0x80, 0x00, 0x00, 0x1e, 0x9d, 0xa2, 0x6c, 0xf1, 0x83, 0xf1, 0x97, 0x84,
326 | 0x7d, 0x2d};
327 | byte[] testpacket = saba(testPacketS);
328 |
329 | Properties r = new Properties();
330 | r.load(new StringReader("crypto-suite=AES_CM_128_HMAC_SHA1_80\nrequired=1\nkey-params=inline:IzdXQaD4zH55rctZ8O+0ip3nX+FKXmuJKgmudPej\n"));
331 | Properties l = new Properties();
332 | l.load(new StringReader("crypto-suite=AES_CM_128_HMAC_SHA1_80\nrequired=1\nkey-params=inline:rpKkWGtGVlqxzzFSaR26P+e1UAC4AduIhJSsNTOK\n"));
333 | SRTCPProtocolImpl testMe = new SRTCPProtocolImpl(r, r);
334 | DatagramPacket pkt = new DatagramPacket(testpacket, testpacket.length);
335 | Log.debug("----------> fake inbound packet");
336 | testMe.inbound(pkt);
337 | Log.debug("----------> verify appendAuth mechanism");
338 | ByteBuffer va = ByteBuffer.wrap(testpacket);
339 | Log.debug("Before Auth: "+getHex(va.array()));
340 | testMe._scOut.deriveKeys(0);
341 | testMe.appendAuth(va);
342 | Log.debug("After Auth: "+getHex(va.array()));
343 |
344 | Log.debug("----------> fake outbound packet");
345 | testMe.out_index = 0x777777;
346 | testMe.sendSR(0x53535353, 1500, 0, 1, 1408);
347 | testMe.sendRR();
348 |
349 | } catch (Throwable t) {
350 | Log.error("Thrown " + t.getMessage());
351 | t.printStackTrace();
352 | }
353 | }
354 |
355 | public void setDS(DatagramSocket ds) {
356 | outDs = ds;
357 | }
358 |
359 | private void encrypt(ByteBuffer bbo, long idx, int ssrc) throws GeneralSecurityException {
360 | _scOut.deriveKeys(idx);
361 | Buffer bbbo = (Buffer) bbo;
362 | int pos = bbbo.position();
363 | int paylen = pos - CLEARHEAD;
364 | int pl = (((paylen / 32) + 2) * 32);
365 | byte[] bin = new byte[paylen];
366 | bbbo.position(CLEARHEAD);
367 | bbo.get(bin, 0, paylen);
368 | ByteBuffer in = ByteBuffer.wrap(bin);
369 | ByteBuffer out = ByteBuffer.allocate(pl);
370 | ByteBuffer pepper = getPepper(ssrc, idx);
371 | _scOut.decipher(in, out, pepper);
372 | bbbo.position(CLEARHEAD);
373 | bbo.put(out.array(),0,paylen);
374 | bbbo.position(pos);
375 | }
376 |
377 |
378 | }
379 |
--------------------------------------------------------------------------------
/src/main/java/com/phono/srtplight/RTPProtocolImpl.java:
--------------------------------------------------------------------------------
1 | package com.phono.srtplight;
2 |
3 | /*
4 | * Copyright 2011 Voxeo Corp.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | */
19 | import java.io.IOException;
20 | import java.net.DatagramPacket;
21 | import java.net.DatagramSocket;
22 | import java.net.InetSocketAddress;
23 | import java.net.SocketAddress;
24 | import java.net.SocketException;
25 | import java.nio.ByteBuffer;
26 | import java.security.SecureRandom;
27 | import java.util.Random;
28 |
29 | public class RTPProtocolImpl extends BitUtils implements RTPProtocolFace {
30 |
31 | final static int RTPHEAD = 12;
32 | final static private int RTPVER = 2;
33 | final static Random _rand = new SecureRandom();
34 | private RTPDataSink _rtpds;
35 | /* inbound state vars */
36 | private long _sync = -1;
37 | protected long _index;
38 | private boolean _first;
39 | protected long _roc = 0; // only used for inbound we _know_ the answer for outbound.
40 | protected char _s_l;// only used for inbound we _know_ the answer for outbound.
41 |
42 | /* networky stuff bidriectional*/
43 | DatagramSocket _ds;
44 | SocketAddress _far;
45 | protected Thread _listen;
46 | String _session;
47 | protected boolean _srtp = false;
48 | int _id;
49 | /* we don't support assymetric codec types (we could I suppose) so this is bi */
50 | int _ptype;
51 |
52 | /* outbound state */
53 | long _seqno;
54 | protected long _csrcid;
55 | protected int _tailIn;
56 | protected int _tailOut;
57 | private int _dtmfType = 101;
58 | private Exception _lastx;
59 | private boolean _realloc = false;
60 | private long[] csrc;
61 | private byte [] extens;
62 | private Character extype;
63 |
64 | public RTPProtocolImpl(int id, DatagramSocket ds, InetSocketAddress far, int type) {
65 | _ds = ds;
66 | _far = far;
67 | _id = id;
68 | _ptype = type;
69 | _session = "RTPSession" + id;
70 | _seqno = 0;
71 | _csrcid = _rand.nextInt();
72 | if (_ds != null) {
73 | try {
74 | if (_far != null) {
75 | if (!far.getAddress().isLoopbackAddress()) {
76 | _ds.connect(_far);
77 | } // if we are talking to loopback we dont need the extra
78 | // security of connecting.
79 | }
80 | _ds.setSoTimeout(100);
81 | } catch (SocketException ex) {
82 | Log.warn("Problem with datagram socket:" + ex.getMessage());
83 | }
84 | } else {
85 | Log.verb("RTPProtocolImpl with no datagram socket");
86 | }
87 |
88 | if (_ds != null) {
89 | // I like to hide the run method, otherwise it is public
90 | Runnable ir = new Runnable() {
91 |
92 | public void run() {
93 | irun();
94 | }
95 |
96 | };
97 | _listen = new Thread(ir);
98 | _listen.setName(_session);
99 | }
100 | _first = true;
101 | Log.debug("RTP session " + this.getClass().getSimpleName() + _session);
102 | }
103 |
104 | public byte [] getExtens(){
105 | return extens;
106 | }
107 | public Character getExtype(){
108 | return extype;
109 | }
110 | public void setSSRC(long v) {
111 | _csrcid = v;
112 | }
113 |
114 | public RTPProtocolImpl(int id, String local_media_address, int local_audio_port, String remote_media_address, int remote_audio_port, int type) throws SocketException {
115 | this(id, new DatagramSocket(local_audio_port), new InetSocketAddress(remote_media_address, remote_audio_port), type);
116 | }
117 |
118 | public void setRealloc(boolean v) {
119 | _realloc = v;
120 | }
121 |
122 | protected void irun() {
123 | byte[] data = new byte[1490];
124 | DatagramPacket dp = new DatagramPacket(data, data.length);
125 | Log.debug("Max Datagram size " + data.length);
126 | Log.debug("address is " + _ds.getLocalSocketAddress().toString());
127 | long count = 0;
128 | while (_listen != null) {
129 | try {
130 | Log.verb("rtp loop");
131 | _ds.receive(dp);
132 | parsePacket(dp);
133 | count++;
134 | if (_realloc) {
135 | data = new byte[1490];
136 | dp = new DatagramPacket(data, data.length);
137 | }
138 | } catch (java.net.SocketTimeoutException x) {
139 | if (count > 0) {
140 | Log.debug("Timeout waiting for packet");
141 | }
142 | } catch (IOException ex) {
143 | Log.debug(this.getClass().getSimpleName() + " " + ex.toString());
144 | _lastx = ex;
145 | }
146 | }
147 | if (!_ds.isClosed()) {
148 | _ds.close();
149 | }
150 | // some tidyup here....
151 | }
152 |
153 | public void setRTPDataSink(RTPDataSink ds) {
154 | _rtpds = ds;
155 | }
156 |
157 | public void terminate() {
158 | _listen = null;
159 | }
160 |
161 | static long get4ByteInt(byte[] b, int offs) {
162 | return ((b[offs++] << 24) | (b[offs++] << 16) | (b[offs++] << 8) | (0xff & b[offs++]));
163 | }
164 |
165 | public void sendPacket(byte[] data, long stamp, int ptype) throws SocketException, IOException {
166 | sendPacket(data, stamp, ptype, false);
167 | }
168 |
169 | public long getIndex() {
170 | return _index;
171 | }
172 |
173 | public long getSeqno() {
174 | return _seqno;
175 | }
176 |
177 | public Exception getNClearLastX() {
178 | Exception ret = _lastx;
179 | ret = null;
180 | return ret;
181 | }
182 |
183 | public void sendPacket(byte[] data, long stamp, int ptype, boolean marker) throws IOException {
184 | sendPacket(data, stamp, (char) _seqno, ptype, marker);
185 | _seqno++;
186 | }
187 |
188 | public void sendPacket(byte[] data, long stamp, char seqno, int ptype, boolean marker) throws IOException {
189 | // skip X
190 | // skip cc
191 | // skip M
192 | // all the above are zero.
193 | try {
194 | byte[] payload = new byte[RTPHEAD + data.length + _tailOut]; // assume no pad and no cssrcs
195 | copyBits(RTPVER, 2, payload, 0); // version
196 | // skip pad
197 | // skip X
198 | // skip cc
199 | // skip M
200 | // all the above are zero.
201 | if (marker) {
202 | copyBits(1, 1, payload, 8);
203 | }
204 | copyBits(ptype, 7, payload, 9);
205 | payload[2] = (byte) (seqno >> 8);
206 | payload[3] = (byte) seqno;
207 | payload[4] = (byte) (stamp >> 24);
208 | payload[5] = (byte) (stamp >> 16);
209 | payload[6] = (byte) (stamp >> 8);
210 | payload[7] = (byte) stamp;
211 | payload[8] = (byte) (_csrcid >> 24);
212 | payload[9] = (byte) (_csrcid >> 16);
213 | payload[10] = (byte) (_csrcid >> 8);
214 | payload[11] = (byte) _csrcid;
215 | for (int i = 0; i < data.length; i++) {
216 | payload[i + RTPHEAD] = data[i];
217 | }
218 | appendAuth(payload);
219 | sendToNetwork(payload);
220 |
221 | Log.verb("sending RTP " + _ptype + " packet length " + payload.length + "seq =" + (int) seqno + " csrc=" + _csrcid + " stamp=" + stamp);
222 | } catch (IOException ex) {
223 | _lastx = ex;
224 | Log.error("Not sending RTP " + _ptype + "ex = " + ex.getMessage());
225 | throw ex;
226 | }
227 |
228 | }
229 |
230 | protected void sendToNetwork(byte[] payload) throws IOException {
231 | DatagramPacket p = (_far == null) ? new DatagramPacket(payload, payload.length)
232 | : new DatagramPacket(payload, payload.length, _far);
233 | _ds.send(p);
234 | }
235 |
236 | protected void parsePacket(DatagramPacket dp) throws IOException {
237 |
238 | // parse RTP header (if we care .....)
239 | /*
240 | * 0 1 2 3
241 | * 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
242 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
243 | * |V=2|P|X| CC |M| PT | sequence number |
244 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
245 | * | timestamp |
246 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
247 | * | synchronization source (SSRC) identifier |
248 | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
249 | * | contributing source (CSRC) identifiers |
250 | * | .... |
251 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
252 | *
253 | */
254 | byte[] packet = dp.getData();
255 | byte[] payload;
256 | int plen = dp.getLength();
257 |
258 | int ver = 0;
259 | int pad = 0;
260 | int csrcn = 0;
261 | int mark = 0;
262 | int ptype = 0;
263 | char seqno = 0;
264 | long stamp = 0;
265 | int sync = 0;
266 | int x = 0;
267 | int exlen = 0;
268 |
269 | Log.verb("got packet " + plen);
270 |
271 | if (plen < 12) {
272 | throw new RTPPacketException("Packet too short. RTP must be >12 bytes");
273 | }
274 | ver = copyBits(packet, 0, 2);
275 | pad = copyBits(packet, 2, 1);
276 | x = copyBits(packet, 3, 1);
277 | csrcn = copyBits(packet, 4, 4);
278 | mark = copyBits(packet, 8, 1);
279 | ptype = copyBits(packet, 9, 7);
280 | ByteBuffer pb = ByteBuffer.wrap(packet);
281 |
282 | seqno = pb.getChar(2);
283 | stamp = getUnsignedInt(pb, 4);
284 | sync = pb.getInt(8);
285 | if (plen < (RTPHEAD + 4 * csrcn)) {
286 | throw new RTPPacketException("Packet too short. CSRN =" + csrcn + " but packet only " + plen);
287 | }
288 |
289 | csrc = new long[csrcn];
290 | int offs = RTPHEAD;
291 | for (int i = 0; i < csrcn; i++) {
292 | csrc[i] = getUnsignedInt(pb, offs);
293 | offs += 4;
294 | }
295 | if (x > 0) {
296 | extype = new Character(pb.getChar(offs));
297 | offs+=2;
298 | exlen = pb.getChar(offs);
299 | offs+=2;
300 | Log.verb("skip an extension 0x"+Integer.toHexString(extype)+" length "+exlen);
301 | extens = new byte[4*exlen];
302 | for (int i=0;i Short.MAX_VALUE) {
365 | // big positive offset
366 | v = _roc - 1; // we wrapped recently and this is an older packet.
367 | }
368 | if (v < 0) {
369 | v = 0; // trap odd initial cases
370 | }
371 | /*
372 | if (_s_l < 32768) {
373 | v = ((seqno - _s_l) > 32768) ? (_roc - 1) % (1 << 32) : _roc;
374 | } else {
375 | v = ((_s_l - 32768) > seqno) ? (_roc + 1) % (1 << 32) : _roc;
376 | }*/
377 | long low = (long) seqno;
378 | long high = ((long) v << 16);
379 | long ret = low | high;
380 | return ret;
381 |
382 | }
383 |
384 | protected void deliverPayload(byte[] payload, long stamp, int ssrc, char seqno) {
385 | if (_rtpds != null) {
386 | _rtpds.dataPacketReceived(payload, stamp, getIndex(seqno));
387 | }
388 | }
389 |
390 | void appendAuth(byte[] payload, char seqno) throws RTPPacketException {
391 | }
392 |
393 | void appendAuth(byte[] payload) throws RTPPacketException {
394 | // nothing to do in rtp
395 | }
396 |
397 | void updateCounters(
398 | char seqno) {
399 | // note that we have seen it.
400 | int diff = seqno - _s_l; // normally we expect this to be 1
401 | if (seqno == 0) {
402 | Log.debug("seqno = 0 _index =" + _index + " _roc =" + _roc + " _s_l= " + (0 + _s_l) + " diff = " + diff + " mins=" + Short.MIN_VALUE);
403 | }
404 | if (diff < Short.MIN_VALUE) {
405 | // large negative offset so
406 | _roc++; // if the old value is more than 2^15 smaller
407 | // then we have wrapped
408 | }
409 | _s_l = seqno;
410 | }
411 |
412 | protected void syncChanged(long sync) throws RTPPacketException {
413 | if (_sync == -1) {
414 | _sync = sync;
415 | } else {
416 | throw new RTPPacketException("Sync changed: was " + _sync + " now " + sync);
417 | }
418 | }
419 |
420 | public static long getUnsignedInt(ByteBuffer bb, int loc) {
421 | return ((long) bb.getInt(loc) & 0xffffffffL);
422 | }
423 |
424 | public static void putUnsignedInt(ByteBuffer bb, long value, int loc) {
425 | bb.putInt(loc, (int) (value & 0xffffffffL));
426 | }
427 |
428 | public void startrecv() {
429 | _listen.start();
430 | }
431 |
432 | public DatagramSocket getDS() {
433 | return _ds;
434 | }
435 |
436 | public boolean finished() {
437 | return _listen == null;
438 | }
439 |
440 | public void sendDigit(String value, long stamp, int samples, int duration) throws SocketException, IOException {
441 | /*
442 | Event encoding (decimal)
443 | _________________________
444 | 0--9 0--9
445 | * 10
446 | # 11
447 | A--D 12--15
448 | Flash 16
449 | *
450 |
451 | The payload format is shown in Fig. 1.
452 |
453 | 0 1 2 3
454 | 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
455 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
456 | | event |E|R| volume | duration |
457 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
458 |
459 | */
460 | int sp = 0;
461 | int end = 0;
462 | int db = 3;
463 | char c = value.toUpperCase().charAt(0);
464 | if (c >= '0' && c <= '9') {
465 | sp = (c - '0');
466 | } else {
467 | if (c == '#') {
468 | sp = 11;
469 | }
470 | if (c == '*') {
471 | sp = 10;
472 | }
473 | }
474 | if ((c >= 'A') && (c <= 'D')) {
475 | sp = (12 + (c - 'A'));
476 | }
477 | byte data[] = new byte[4];
478 |
479 |
480 | /*
481 | data[0] = (byte) ((0xff) & (sp | 0x80)); // set End flag
482 | data[1] = 0 ; // 0db - LOUD
483 | data[3] = (byte) ((0xff) & (dur));
484 | data[2] = (byte) ((0xff) & (dur >> 8)) ;
485 | *
486 | */
487 | copyBits(sp, 8, data, 0);
488 | copyBits(end, 0, data, 8);
489 | copyBits(db, 6, data, 10);
490 | copyBits(samples, 16, data, 16);
491 |
492 | sendDTMFData(data, stamp, true);
493 |
494 | // try to ensure that the time between messages is slightly less than the
495 | // selected 'duration'
496 | long count = (duration / 20) - 1;
497 | for (int i = 0; i < count; i++) {
498 | try {
499 | Thread.sleep(10);
500 | sendDTMFData(data, stamp, false);// send an update
501 | Thread.sleep(10);
502 |
503 | } catch (InterruptedException ex) {
504 | Log.verb(ex.getMessage());
505 | }
506 | }
507 | //stupid ugly mess - fixed stamp on multiple packets
508 | //stamp = fac * _audio.getOutboundTimestamp();
509 | end = 1;
510 | copyBits(end, 1, data, 8);
511 | sendDTMFData(data, stamp, false);
512 | sendDTMFData(data, stamp, false);
513 | sendDTMFData(data, stamp, false);
514 |
515 | }
516 |
517 | public boolean sendDTMFData(byte[] data, long stamp, boolean mark) throws SocketException, IOException {
518 | boolean ret = false;
519 | sendPacket(data, stamp, _dtmfType, mark);
520 | ret = true;
521 | return ret;
522 | }
523 |
524 | public void setDTMFPayloadType(int type) {
525 | _dtmfType = type;
526 | }
527 |
528 | protected void deliverPayload(byte[] payload, long stamp, int sync, char seqno, int mark) {
529 | deliverPayload(payload, stamp, sync, seqno);
530 | }
531 |
532 | public static void main(String[] args) {
533 | // loop back test
534 | byte data[] = new byte[1209];
535 | SecureRandom sr = new SecureRandom();
536 | sr.nextBytes(data);
537 | long stamp = 0;
538 | int id;
539 | int type;
540 | final DatagramPacket[] dsa = new DatagramPacket[1];
541 | final long gstamp[] = new long[1];
542 | final long gindex[] = new long[1];
543 | try {
544 | DatagramSocket ds = new DatagramSocket() {
545 | @Override
546 | public void send(DatagramPacket dp) throws IOException {
547 | dsa[0] = dp;
548 | }
549 | };
550 | id = sr.nextInt(Character.MAX_VALUE);
551 | type = sr.nextInt(Byte.MAX_VALUE);
552 | RTPProtocolImpl target = new RTPProtocolImpl(id, ds, null, type);
553 | RTPDataSink rtpds = new RTPDataSink() {
554 | @Override
555 | public void dataPacketReceived(byte[] data, long stamp, long index) {
556 | gstamp[0] = stamp;
557 | gindex[0] = index;
558 | }
559 | };
560 | target.setRTPDataSink(rtpds);
561 | while (stamp < 0x200000000L) {
562 | target.sendPacket(data, stamp, type);
563 | target.parsePacket(dsa[0]);
564 | if (gstamp[0] != stamp) {
565 | throw new java.lang.ArithmeticException("Stamp is wrong " + gstamp[0] + " != " + stamp);
566 | }
567 | long xindex = stamp;
568 | if (gindex[0] != xindex) {
569 | throw new java.lang.ArithmeticException("Index is wrong " + gindex[0] + " != " + xindex);
570 | }
571 | if ((stamp % 0x1000000) == 0) {
572 | System.out.println("did " + stamp + " tests");
573 | }
574 | stamp++;
575 | }
576 | } catch (Exception x) {
577 | System.out.println("exception " + x.getLocalizedMessage());
578 |
579 | x.printStackTrace();
580 | }
581 | System.out.println("did " + stamp + " tests");
582 |
583 | }
584 | }
585 |
--------------------------------------------------------------------------------
/src/main/java/com/phono/srtplight/SRTPProtocolImpl.java:
--------------------------------------------------------------------------------
1 | package com.phono.srtplight;
2 |
3 | /*
4 | * Copyright 2011 Voxeo Corp.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | */
19 | import java.io.*;
20 | import java.io.IOException;
21 | import java.net.DatagramSocket;
22 | import java.net.InetSocketAddress;
23 | import java.net.SocketException;
24 | import java.nio.Buffer;
25 | import java.nio.ByteBuffer;
26 | import java.security.GeneralSecurityException;
27 | import java.util.Properties;
28 | import javax.crypto.Mac;
29 |
30 | /**
31 | * see http://www.faqs.org/rfcs/rfc3711.html
32 | */
33 | public class SRTPProtocolImpl extends RTPProtocolImpl {
34 |
35 | final static int SRTPWINDOWSIZE = 64;
36 |
37 | ;
38 | long[] _replay = new long[SRTPWINDOWSIZE];
39 | long _windowLeadingEdge = 0;
40 |
41 | ;
42 | private SRTPSecContext _scIn;
43 | private SRTPSecContext _scOut;
44 | private boolean _doCrypt = true;
45 | private boolean _doAuth = true;
46 |
47 | private void init(Properties lcryptoProps, Properties rcryptoProps) {
48 | _srtp = true;
49 | _scIn = _scOut = null;
50 |
51 | try {
52 | if (_doAuth || _doCrypt) {
53 | _scIn = new SRTPSecContext(true);
54 | _scIn.parseCryptoProps(rcryptoProps);
55 | _tailIn = _scIn.getAuthTail();
56 | _scOut = new SRTPSecContext(false);
57 | _scOut.parseCryptoProps(lcryptoProps);
58 | _tailOut = _scOut.getAuthTail();
59 | }
60 | } catch (GeneralSecurityException ex) {
61 | Log.error(" error in constructor " + ex.getMessage());
62 | ex.printStackTrace();
63 | }
64 | }
65 | // we are _trusting_ the kernel to only send us the 'correct' packets, no others.
66 | // we check of course, but we depend on it to route the packets, so there is no
67 | // need to have a separate list of crypto contexts as envisaged in the RFC
68 |
69 | public SRTPProtocolImpl(int id, DatagramSocket ds, InetSocketAddress far, int type, Properties lcryptoProps, Properties rcryptoProps) {
70 | super(id, ds, far, type);
71 | init(lcryptoProps, rcryptoProps);
72 | }
73 |
74 | public SRTPProtocolImpl(int id, String local_media_address, int local_audio_port,
75 | String remote_media_address, int remote_audio_port,
76 | int type, Properties lcryptoProps, Properties rcryptoProps) throws SocketException {
77 | super(id, local_media_address, local_audio_port, remote_media_address, remote_audio_port, type);
78 | init(lcryptoProps, rcryptoProps);
79 | }
80 |
81 | /*
82 | The format of an SRTP packet is illustrated in Figure 1.
83 |
84 | 0 1 2 3
85 | 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
86 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<+
87 | |V=2|P|X| CC |M| PT | sequence number | |
88 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
89 | | timestamp | |
90 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
91 | | synchronization source (SSRC) identifier | |
92 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ |
93 | | contributing source (CSRC) identifiers | |
94 | | .... | |
95 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
96 | | RTP extension (OPTIONAL) | |
97 | +>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
98 | | | payload ... | |
99 | | | +-------------------------------+ |
100 | | | | RTP padding | RTP pad count | |
101 | +>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<+
102 | | ~ SRTP MKI (OPTIONAL) ~ |
103 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
104 | | : authentication tag (RECOMMENDED) : |
105 | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
106 | | |
107 | +- Encrypted Portion* Authenticated Portion ---+
108 |
109 | *
110 | */
111 | /*
112 |
113 | To authenticate and decrypt an SRTP packet, the receiver SHALL do the
114 | following:
115 |
116 | 1. Determine which cryptographic context to use as described in
117 | Section 3.2.3.
118 |
119 | 2. Run the algorithm in Section 3.3.1 to get the index of the SRTP
120 | packet. The algorithm uses the rollover counter and highest
121 | sequence number in the cryptographic context with the sequence
122 | number in the SRTP packet, as described in Section 3.3.1.
123 |
124 | 3. Determine the master key and master salt. If the MKI indicator in
125 | the context is set to one, use the MKI in the SRTP packet,
126 | otherwise use the index from the previous step, according to
127 | Section 8.1.
128 |
129 | 4. Determine the session keys, and session salt (if used by the
130 | transform) as described in Section 4.3, using master key, master
131 | salt, key_derivation_rate and session key-lengths in the
132 | cryptographic context with the index, determined in Steps 2 and 3.
133 |
134 | 5. For message authentication and replay protection, first check if
135 | the packet has been replayed (Section 3.3.2), using the Replay
136 | List and the index as determined in Step 2. If the packet is
137 | judged to be replayed, then the packet MUST be discarded, and the
138 | event SHOULD be logged.
139 |
140 | Next, perform verification of the authentication tag, using the
141 | rollover counter from Step 2, the authentication algorithm
142 | indicated in the cryptographic context, and the session
143 | authentication key from Step 4. If the result is "AUTHENTICATION
144 | FAILURE" (see Section 4.2), the packet MUST be discarded from
145 | further processing and the event SHOULD be logged.
146 |
147 | 6. Decrypt the Encrypted Portion of the packet (see Section 4.1, for
148 | the defined ciphers), using the decryption algorithm indicated in
149 | the cryptographic context, the session encryption key and salt (if
150 | used) found in Step 4 with the index from Step 2.
151 |
152 | 7. Update the rollover counter and highest sequence number, s_l, in
153 | the cryptographic context as in Section 3.3.1, using the packet
154 | index estimated in Step 2. If replay protection is provided, also
155 | update the Replay List as described in Section 3.3.2.
156 |
157 | 8. When present, remove the MKI and authentication tag fields from
158 | the packet.
159 |
160 | */
161 | void checkForReplay() throws RTPPacketException {
162 | // index is set by now...
163 | if (_index < _windowLeadingEdge) {
164 | // old packet....
165 | if ((_windowLeadingEdge - _index) > SRTPWINDOWSIZE) {
166 | throw new RTPPacketException(" out of window, packet too old");
167 | }
168 | // in window but .... is it a replay ?
169 | int tidx = (int) (_index % SRTPWINDOWSIZE);
170 | if (_replay[tidx] == _index) {
171 | throw new RTPPacketException(" Seen that packet before - replay attack ? " + _index);
172 | }
173 | }
174 | }
175 |
176 | @Override
177 | void checkAuth(byte[] packet, int plen) throws RTPPacketException {
178 | if (Log.getLevel() > Log.DEBUG) {
179 |
180 | Log.verb("auth on packet " + getHex(packet, plen));
181 | Log.verb("Packet index " + Long.toHexString(_index));
182 |
183 | }
184 | try {
185 | _scIn.deriveKeys(0);
186 |
187 | if (_doAuth) {
188 | Mac hmac = _scIn.getAuthMac();
189 |
190 | int alen = _tailIn;
191 | int offs = plen - alen;
192 | ByteBuffer m = ByteBuffer.allocate(offs + 4);
193 | m.put(packet, 0, offs);
194 | m.putInt((int) _roc);
195 |
196 | byte[] auth = new byte[alen];
197 | System.arraycopy(packet, offs, auth, 0, alen);
198 | int mlen = (plen - RTPHEAD) - alen;
199 | Log.verb("mess length =" + mlen);
200 | if (Log.getLevel() > Log.DEBUG) {
201 | Log.verb("auth body " + getHex(m.array()));
202 | }
203 | ((Buffer)m).position(0);
204 | hmac.update(m);
205 | byte[] mac = hmac.doFinal();
206 |
207 | if (Log.getLevel() > Log.DEBUG) {
208 | Log.verb("auth in " + getHex(auth));
209 | }
210 | if (Log.getLevel() > Log.DEBUG) {
211 | Log.verb("auth out " + getHex(mac, 10));
212 | }
213 |
214 | for (int i = 0; i < alen; i++) {
215 | if (auth[i] != mac[i]) {
216 | throw new RTPPacketException("not authorized byte " + i + " does not match ");
217 | }
218 | }
219 | }
220 | } catch (GeneralSecurityException ex) {
221 |
222 | throw new RTPPacketException("Problem checking packet " + ex.getMessage());
223 |
224 | }
225 | }
226 |
227 | @Override
228 | protected void deliverPayload(
229 | byte[] payload, long stamp, int ssrc, char seqno) {
230 | try {
231 | if (_doCrypt) {
232 | decrypt(payload, ssrc);
233 | }
234 | super.deliverPayload(payload, stamp, ssrc, seqno);
235 | } catch (GeneralSecurityException ex) {
236 | Log.error("problem with decryption " + ex.getMessage());
237 | }
238 | }
239 |
240 | @Override
241 | void updateCounters(
242 | char seqno) {
243 | // note that we have seen it.
244 | int tidx = (int) (_index % SRTPWINDOWSIZE);
245 | _replay[tidx] = _index;
246 | // and update the leading edge if needed.
247 | if (_index > _windowLeadingEdge) {
248 | _windowLeadingEdge = _index;
249 | }
250 | super.updateCounters(seqno);
251 | }
252 |
253 | @Override
254 | /**
255 | * calculate the outbound auth and put it at the end of the packet starting
256 | * at length - _tail space is already allocated.
257 | */
258 | void appendAuth(byte[] packet) throws RTPPacketException {
259 |
260 | if (_doAuth) {
261 | try {
262 | // strictly we might need to derive the keys here too -
263 | // since we might be doing auth but no crypt.
264 | // we don't support that so nach.
265 | Mac mac = _scOut.getAuthMac();
266 | int offs = packet.length - _tailOut;
267 | ByteBuffer m = ByteBuffer.allocate(offs + 4);
268 | m.put(packet, 0, offs);
269 | int oroc = (int) (_seqno >>> 16);
270 | if ((_seqno & 0xffff) == 0) {
271 | Log.debug("seqno = 0 outgoing roc =" + oroc);
272 | }
273 | m.putInt(oroc);
274 | if (Log.getLevel() > Log.DEBUG) {
275 | Log.verb("auth body " + getHex(m.array()));
276 | }
277 | ((Buffer)m).position(0);
278 |
279 | mac.update(m);
280 | byte[] auth = mac.doFinal();
281 | int len = _tailOut;
282 |
283 | for (int i = 0; i < len; i++) {
284 | packet[offs + i] = auth[i];
285 | }
286 | } catch (GeneralSecurityException ex) {
287 | throw new RTPPacketException("Problem sending packet " + ex.getMessage());
288 | }
289 | }
290 | if (Log.getLevel() > Log.DEBUG) {
291 | Log.verb("Sending packet " + getHex(packet));
292 | }
293 | }
294 |
295 | Character oseq = null;
296 | int roc = 0;
297 | static final int wrapdiff = 2 << 14;
298 |
299 | /**
300 | * warning assumes payload is correctly encrypted - which is specious
301 | *
302 | * @param data
303 | * @param stamp
304 | * @param seqno
305 | * @param ptype
306 | * @param marker
307 | * @throws SocketException
308 | * @throws IOException
309 | */
310 | public void reSendEncryptedPacket(byte[] data, long stamp, long seqno, int ptype, boolean marker) throws SocketException, IOException {
311 | super.sendPacket(data, stamp, (char)seqno, ptype, marker);
312 | }
313 | /**
314 | * do this without changing _any_ state.
315 | * @param data
316 | * @param stamp
317 | * @param seq
318 | * @param ptype
319 | * @param marker
320 | * @throws SocketException
321 | * @throws IOException
322 | */
323 | public void reSendUnEncryptedPacket(byte[] data, long stamp, long seq, int ptype, boolean marker) throws SocketException, IOException {
324 | if (_doCrypt) {
325 | try {
326 | _scOut.deriveKeys(seq);
327 | encrypt(data, (int) _csrcid, seq);
328 | super.sendPacket(data, stamp, (char) seq, ptype, marker);
329 | } catch (GeneralSecurityException ex) {
330 | Log.error("problem encrypting packet" + ex.getMessage());
331 | ex.printStackTrace();
332 | }
333 | }
334 | }
335 |
336 | @Override
337 | /*
338 | variant that sets seqno itself rather than incrementing it .
339 | which requires a small amount of guesswork
340 | */
341 | public void sendPacket(byte[] data, long stamp, char seqno, int ptype, boolean marker) throws SocketException, IOException {
342 | int n = roc;
343 | if (oseq == null) {
344 | oseq = seqno;
345 | }
346 | /* check for wrap or unwrap */
347 | int diff = seqno - oseq;
348 | if (diff < (-wrapdiff)) {
349 | // huge negative diff between seqs
350 | // assume we wrapped
351 | roc++;
352 | n = roc;
353 | Log.debug(" wrapped seqno " + (int) seqno + " oseq " + (int) oseq + " diff =" + diff + " outgoing roc =" + n);
354 |
355 | }
356 | if (diff > wrapdiff) {
357 | // big positive
358 | // pre-wrap packet was out of order
359 | n = roc - 1;
360 | Log.debug(" unwrapped seqno " + (int) seqno + " oseq " + (int) oseq + " diff =" + diff + " outgoing roc =" + n);
361 | }
362 | oseq = seqno;
363 | long low = (long) seqno;
364 | long high = ((long) n << 16);
365 | _seqno = low | high;
366 |
367 | if (_doCrypt) {
368 | try {
369 | _scOut.deriveKeys(_seqno);
370 | encrypt(data, (int) _csrcid, _seqno);
371 | super.sendPacket(data, stamp, (char) _seqno, ptype, marker);
372 | } catch (GeneralSecurityException ex) {
373 | Log.error("problem encrypting packet" + ex.getMessage());
374 | ex.printStackTrace();
375 | }
376 | }
377 |
378 | }
379 |
380 | @Override
381 | public void sendPacket(byte[] data, long stamp, int ptype, boolean marker) throws SocketException,
382 | IOException {
383 | try {
384 | if (_doCrypt) {
385 | _scOut.deriveKeys(stamp);
386 | encrypt(data, (int) _csrcid, _seqno);
387 | }
388 | super.sendPacket(data, stamp, ptype, marker);
389 | } catch (GeneralSecurityException ex) {
390 | Log.error("problem encrypting packet" + ex.getMessage());
391 | ex.printStackTrace();
392 | }
393 | }
394 |
395 | static ByteBuffer getPepper(int ssrc, long idx) {
396 | //(SSRC * 2^64) XOR (i * 2^16)
397 | ByteBuffer pepper = ByteBuffer.allocate(16);
398 | pepper.putInt(4, ssrc);
399 | long sindex = idx << 16;
400 | pepper.putLong(8, sindex);
401 |
402 | return pepper;
403 | }
404 |
405 | private void decrypt(byte[] payload, int ssrc) throws GeneralSecurityException {
406 | ByteBuffer in = ByteBuffer.wrap(payload);
407 | // aes likes the buffer a multiple of 32 and longer than the input.
408 | int pl = (((payload.length / 32) + 2) * 32);
409 | ByteBuffer out = ByteBuffer.allocate(pl);
410 | ByteBuffer pepper = getPepper(ssrc, _index);
411 | _scIn.decipher(in, out, pepper);
412 | System.arraycopy(out.array(), 0, payload, 0, payload.length);
413 | }
414 |
415 | private void encrypt(byte[] payload, int ssrc, long idx) throws GeneralSecurityException {
416 | ByteBuffer in = ByteBuffer.wrap(payload);
417 | int pl = (((payload.length / 32) + 2) * 32);
418 | ByteBuffer out = ByteBuffer.allocate(pl);
419 | ByteBuffer pepper = getPepper(ssrc, idx);
420 | _scOut.decipher(in, out, pepper);
421 | System.arraycopy(out.array(), 0, payload, 0, payload.length);
422 | }
423 |
424 | public static void main(String[] args) {
425 | System.out.println("testing STRP ");
426 | int id = 99;
427 | String local_media_address = "127.0.0.1";
428 | int local_audio_port = 19000;
429 | String remote_media_address = "127.0.0.1";
430 | int remote_audio_port = 19001;
431 | int type = 1;
432 | Log.setLevel(Log.VERB);
433 | String sdp = "required='1' \n"
434 | + "crypto-suite='AES_CM_128_HMAC_SHA1_80' \n"
435 | + "key-params='inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj' \n"
436 | + "session-params='KDR=0' \n"
437 | + "tag='1' \n";
438 |
439 | InputStream sr = new ByteArrayInputStream(sdp.getBytes());
440 | Properties cryptoProps = new Properties();
441 | try {
442 | cryptoProps.load(sr);
443 | } catch (IOException ex) {
444 | Log.error("invalid sdp props.");
445 | }
446 | SRTPProtocolImpl s = null;
447 | try {
448 | s = new SRTPProtocolImpl(id, local_media_address, local_audio_port,
449 | remote_media_address, remote_audio_port,
450 | type, cryptoProps, cryptoProps);
451 |
452 | //s.testSeqs();
453 | //s.testRcvSRTP();
454 | s.testSendSRTP();
455 | } catch (IOException ex) {
456 | Log.error(ex.getMessage());
457 | }
458 | try {
459 | Thread.sleep(60000);
460 | } catch (InterruptedException ex) {
461 | ;
462 | }
463 | if (s != null) {
464 | s.terminate();
465 | }
466 |
467 | }
468 |
469 | private void testRcvSRTP() {
470 |
471 | // need to make a more promiscuous socket on a known port
472 | _ds.close();
473 | try {
474 | _ds = new DatagramSocket(19000);
475 | _ds.setSoTimeout(1000);
476 | } catch (SocketException ex) {
477 | ex.printStackTrace();
478 | }
479 |
480 | Log.debug("to test rcv of SRTP packets run");
481 | Log.debug("./rtpw -a -e -k " + getHex(_scIn._masterKey) + getHex(_scIn._masterSalt.array()) + " -s "
482 | + "127.0.0.1 " + _ds.getLocalPort());
483 |
484 | Log.debug("test srtp recv starting in 10 secs");
485 | try {
486 | Thread.sleep(10000);
487 | } catch (InterruptedException ex) {
488 | ;
489 | }
490 |
491 | RTPDataSink sink = new RTPDataSink() {
492 |
493 | public void dataPacketReceived(byte[] data, long stamp, long idx) {
494 | Log.debug("got " + data.length + " bytes");
495 | Log.debug("data =" + getHex(data));
496 | Log.debug("Message is " + new String(data));
497 | }
498 | };
499 | setRTPDataSink(sink);
500 | startrecv();
501 | }
502 |
503 | private void testSeqs() {
504 | try {
505 | char seq = 0;
506 | System.out.println("Seq test ");
507 | long top = Integer.MAX_VALUE;
508 | for (long j = 0; j < top; j++) {
509 |
510 | long i = getIndex(seq);
511 | if (i != j) {
512 | throw new RTPPacketException("sequence test failed " + i + " != " + j + " seq " + (short) seq);
513 | }
514 | _index = i;
515 | checkForReplay();
516 | updateCounters(seq);
517 | seq++;
518 |
519 | }
520 | } catch (RTPPacketException ex) {
521 | Log.debug(ex.getMessage());
522 | }
523 | }
524 |
525 | public static String getHex(byte[] in) {
526 | return getHex(in, in.length);
527 | }
528 |
529 | public static String getHex(byte[] in, int len) {
530 | char cmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
531 | StringBuffer ret = new StringBuffer();
532 | int top = Math.min(in.length, len);
533 | for (int i = 0; i < top; i++) {
534 | ret.append(cmap[0x0f & (in[i] >>> 4)]);
535 | ret.append(cmap[in[i] & 0x0f]);
536 | }
537 | return ret.toString();
538 | }
539 |
540 | private void testSendSRTP() throws IOException {
541 |
542 | // need to make a more promiscuous socket on a known port
543 | _ds.close();
544 | try {
545 | _ds = new DatagramSocket(19000);
546 | _ds.setSoTimeout(60000);
547 | } catch (SocketException ex) {
548 | ex.printStackTrace();
549 | }
550 |
551 | Log.debug("to test rcv of SRTP packets run");
552 | Log.debug("./rtpw -d -a -e -k " + getHex(_scIn._masterKey) + getHex(_scIn._masterSalt.array(), 14) + " -r "
553 | + "127.0.0.1 19002");
554 |
555 | Log.debug("test srtp send starting in 10 secs");
556 | try {
557 | Thread.sleep(10000);
558 | } catch (InterruptedException ex) {
559 | ;
560 | }
561 | // kinda don'r expect this.....
562 | RTPDataSink sink = new RTPDataSink() {
563 |
564 | public void dataPacketReceived(byte[] data, long stamp, long idx) {
565 | Log.debug("got " + data.length + " bytes");
566 | Log.debug("data =" + getHex(data));
567 | Log.debug("Message is " + new String(data));
568 | }
569 | };
570 | setRTPDataSink(sink);
571 |
572 | this._csrcid = 0xdeadbeef;
573 | startrecv();
574 | byte[] data = new byte[33];
575 | long stamp = 0;
576 | String messages[] = {"A",
577 | "a",
578 | "aa",
579 | "aal",
580 | "aalii",
581 | "aam",
582 | "Aani",
583 | "aardvark",
584 | "aardwolf",
585 | "Aaron"
586 | };
587 | for (int i = 0; i < messages.length; i++) {
588 | stamp += 8000;
589 | byte mess[] = new byte[messages[i].length() + 2];
590 | System.arraycopy(messages[i].getBytes(), 0, mess, 0, messages[i].length());
591 | mess[messages[i].length()] = (byte) 10;
592 | Log.debug("Sending " + messages[i]);
593 | sendPacket(mess, stamp, 1);
594 | try {
595 | Thread.sleep(1000);
596 | } catch (InterruptedException ex) {
597 | ;
598 | }
599 | }
600 |
601 | }
602 | }
603 |
--------------------------------------------------------------------------------
/src/main/java/com/phono/srtplight/RTCP.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 |pipe|
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | package com.phono.srtplight;
18 |
19 | import static com.phono.srtplight.SRTPProtocolImpl.getHex;
20 | import java.nio.Buffer;
21 | import java.nio.ByteBuffer;
22 | import java.util.ArrayList;
23 | import java.util.List;
24 |
25 | /**
26 | *
27 | * @author thp
28 | */
29 | public class RTCP {
30 |
31 | final static int SR = 200;
32 | final static int RR = 201;
33 | final static int SDES = 202;
34 | final static int BYE = 203;
35 | final static int RTPFB = 205;
36 | final static int PSFB = 206;
37 |
38 |
39 |
40 | protected char pt;
41 | protected long ssrc;
42 |
43 | void addBody(ByteBuffer bb) {
44 | int pad = 0;
45 | int rc = this.getRC();
46 | int llen = this.estimateBodyLength();
47 | byte[] head = new byte[4];
48 | BitUtils.copyBits(2, 2, head, 0);
49 | BitUtils.copyBits(pad, 1, head, 2);
50 | BitUtils.copyBits(rc, 5, head, 3);
51 | BitUtils.copyBits(pt, 8, head, 8);
52 | BitUtils.copyBits(llen, 16, head, 16);
53 | bb.put(head);
54 | bb.putInt((int) ssrc);
55 | }
56 |
57 | /*
58 | 0 1 2 3
59 | 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
60 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
61 | header |V=2|P| RC | PT=SR=200 | length |
62 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
63 | | SSRC of sender |
64 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
65 | sender | NTP timestamp, most significant word |
66 | info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
67 | | NTP timestamp, least significant word |
68 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
69 | | RTP timestamp |
70 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
71 | | sender's packet count |
72 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
73 | | sender's octet count |
74 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
75 | report | SSRC_1 (SSRC of first source) |
76 | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
77 | 1 | fraction lost | cumulative number of packets lost |
78 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
79 | | extended highest sequence number received |
80 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
81 | | interarrival jitter |
82 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
83 | | last SR (LSR) |
84 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
85 | | delay since last SR (DLSR) |
86 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
87 | report | SSRC_2 (SSRC of second source) |
88 | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
89 | 2 : ... :
90 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
91 | | profile-specific extensions |
92 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
93 | */
94 | static public RTCP mkRTCP(ByteBuffer bb) throws InvalidRTCPPacketException {
95 | RTCP ret = null;
96 | int begin = ((Buffer) bb).position();
97 | char fh = bb.getChar();
98 | int v = (fh & ((char) (0xc000))) >>> 14;
99 | int p = (fh & ((char) (0x2000))) >>> 13;
100 | int rc = (fh & ((char) (0x1f00))) >>> 8;
101 | int lpt = (char) (fh & ((char) (0x00ff)));
102 | int length = bb.getChar();
103 | Log.verb("Have RTCP pkt with v=" + v + " p=" + p + " rc=" + rc + " pt=" + lpt + " lenght=" + length);
104 | if (v != 2) {
105 | throw new InvalidRTCPPacketException("version must be 2");
106 | }
107 | int offset = (length * 4);
108 | switch (lpt) {
109 | case SR:
110 | ret = new SenderReport(bb, rc, length);
111 | break;
112 | case RR:
113 | ret = new ReceiverReport(bb, rc, length);
114 | break;
115 | case SDES:
116 | ret = new SDES(bb, rc, length);
117 | break;
118 | case BYE:
119 | ret = new BYE(bb, rc, length);
120 | break;
121 | case RTPFB:
122 | ret = new RTPFB(bb, rc, length);
123 | break;
124 | case PSFB:
125 | ret = new PSFB(bb, rc, length);
126 | break;
127 | default:
128 | ret = new RTCP();
129 | Log.debug("Ignoring unknown RTCP type =" + lpt);
130 | break;
131 | }
132 | ((Buffer) bb).position(begin + offset + 4);
133 | return ret;
134 | }
135 |
136 | public static void main(String[] args) {
137 | Log.setLevel(Log.ALL);
138 | byte[] sr = {(byte) 0x80, (byte) 0xc8, (byte) 0x00, (byte) 0x06, (byte) 0x50, (byte) 0xf6, (byte) 0xb8,
139 | (byte) 0xbf, (byte) 0xdd, (byte) 0x62, (byte) 0x47, (byte) 0xcd, (byte) 0x4b, (byte) 0x43, (byte) 0x95, (byte) 0x81,
140 | (byte) 0x27, (byte) 0x9b, (byte) 0xca, (byte) 0xef, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
141 | (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00};
142 | byte[] rr = {(byte) 0x81, (byte) 0xc9, (byte) 0x00, (byte) 0x07, (byte) 0xab, (byte) 0x36, (byte) 0xcd,
143 | (byte) 0x19, (byte) 0x2e, (byte) 0x0f, (byte) 0x36, (byte) 0x14, (byte) 0xa0, (byte) 0xfb, (byte) 0xf0,
144 | (byte) 0xe5, (byte) 0x5a, (byte) 0x50, (byte) 0x0b, (byte) 0xc0, (byte) 0x1a, (byte) 0xc9, (byte) 0x52,
145 | (byte) 0xbc, (byte) 0x61, (byte) 0x36, (byte) 0x57, (byte) 0xd5, (byte) 0x5f, (byte) 0x19, (byte) 0x00, (byte) 0xa4
146 | };
147 | byte[] fb = {(byte) 0x8f, (byte) 0xcd, (byte) 0x00, (byte) 0x06, (byte) 0x75, (byte) 0xe8, (byte) 0x1d, (byte) 0x8f,
148 | (byte) 0x04, (byte) 0xad, (byte) 0xa0, (byte) 0xf2, (byte) 0xda, (byte) 0x59, (byte) 0x26, (byte) 0x25, (byte) 0x30,
149 | (byte) 0xee, (byte) 0x6e, (byte) 0x9f, (byte) 0x71, (byte) 0x36, (byte) 0x82, (byte) 0x61, (byte) 0xfb, (byte) 0xe4,
150 | (byte) 0x12, (byte) 0x80};
151 | byte[][] tests = {sr, rr, fb};
152 | for (byte[] t : tests) {
153 | ByteBuffer p = ByteBuffer.wrap(t);
154 | try {
155 | RTCP r = mkRTCP(p);
156 | if (r != null) {
157 | Log.verb(r.toString());
158 | } else {
159 | Log.error("r is null");
160 | }
161 | } catch (InvalidRTCPPacketException irp) {
162 | Log.error(irp.getMessage());
163 | irp.printStackTrace();
164 | }
165 | }
166 | SenderReport sro = mkSenderReport();
167 | sro.setSSRC(1358346431);
168 | sro.setNTPStamp(-2494352296553245311L);
169 | sro.setRTPStamp(664521455);
170 | sro.setSenderPackets(0);
171 | sro.setSenderOctets(0);
172 | int ebl = sro.estimateBodyLength();
173 | ByteBuffer bbo = ByteBuffer.allocate(4 * (ebl + 1));
174 | sro.addBody(bbo);
175 | byte[] pky = bbo.array();
176 | Log.verb("sro " + sro);
177 | Log.verb("sro " + getHex(pky));
178 | for (int i = 0; i < sr.length; i++) {
179 | if (pky[i] != sr[i]) {
180 | Log.error("packets differ at:" + i);
181 | }
182 | }
183 | ReceiverReport rro = mkReceiverReport();
184 | ebl = sro.estimateBodyLength();
185 | bbo = ByteBuffer.allocate(4 * (ebl + 1));
186 | rro.addBody(bbo);
187 | pky = bbo.array();
188 | Log.verb("rro " + rro);
189 | Log.verb("rro " + getHex(pky));
190 |
191 | PSFB p = mkPSFB();
192 | p.setSSRC(0x12345678);
193 | p.setMssrc(0x23456789);
194 | p.setSssrc(0x3456789A);
195 | p.setFmt(1);
196 | p.setFci(new byte[0]);
197 | int pl = p.estimateBodyLength();
198 | bbo = ByteBuffer.allocate(4 * (pl + 1));
199 | p.addBody(bbo);
200 | byte[] pa = bbo.array();
201 | Log.info("PSFB " + p);
202 | Log.info("PSFB " + getHex(pa));
203 | try {
204 | bbo.flip();
205 | RTCP p2 = mkRTCP(bbo);
206 | Log.info("PSFB "+p2.toString());
207 | } catch (Exception x){
208 | Log.warn("cant parse PSFB");
209 | x.printStackTrace();
210 | }
211 |
212 | }
213 |
214 | public static SenderReport mkSenderReport() {
215 | SenderReport ret = new SenderReport();
216 | return ret;
217 | }
218 |
219 | static ReceiverReport mkReceiverReport() {
220 | ReceiverReport ret = new ReceiverReport();
221 | return ret;
222 | }
223 |
224 | static PSFB mkPSFB() {
225 | PSFB ret = new PSFB();
226 | return ret;
227 | }
228 | int estimateBodyLength() { // this is int 32s -1
229 | return 1;
230 | }
231 |
232 | public void setSSRC(long s) {
233 | ssrc = s;
234 | }
235 |
236 | int getRC() {
237 | return 0;
238 | }
239 |
240 | static class SenderReport extends RTCP {
241 |
242 | /*
243 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
244 | | SSRC of sender |
245 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
246 | sender | NTP timestamp, most significant word |
247 | info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
248 | | NTP timestamp, least significant word |
249 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
250 | | RTP timestamp |
251 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
252 | | sender's packet count |
253 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
254 | | sender's octet count |
255 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
256 | */
257 | long ntpstamp;
258 | long rtpstamp;
259 | long senderPkts;
260 | long senderOcts;
261 | ArrayList reports;
262 |
263 | public void setNTPStamp(long s) {
264 | ntpstamp = s;
265 | }
266 |
267 | public void setRTPStamp(long s) {
268 | rtpstamp = s;
269 | }
270 |
271 | public void setSenderPackets(long s) {
272 | senderPkts = s;
273 | }
274 |
275 | public void setSenderOctets(long s) {
276 | senderOcts = s;
277 | }
278 |
279 | public void addReport(ReportBlock b) {
280 | reports.add(b);
281 | }
282 |
283 | @Override
284 | public void addBody(ByteBuffer bb) {
285 | super.addBody(bb);
286 | bb.putLong(ntpstamp);
287 | bb.putInt((int) rtpstamp);
288 | bb.putInt((int) senderPkts);
289 | bb.putInt((int) senderOcts);
290 | for (ReportBlock r : reports) {
291 | r.addBody(bb);
292 | }
293 | }
294 |
295 | @Override
296 | int getRC() {
297 | return reports.size();
298 | }
299 |
300 | @Override
301 | int estimateBodyLength() {
302 | return 6 + (6 * reports.size());
303 | }
304 |
305 | public SenderReport(ByteBuffer bb, int rc, int length) throws InvalidRTCPPacketException {
306 | int expected = 6 + (6 * rc);
307 | if (length != expected) {
308 | throw new InvalidRTCPPacketException("length mismatch expected=" + expected + " got length=" + length);
309 | }
310 |
311 | ssrc = bb.getInt();
312 | ntpstamp = bb.getLong();
313 | rtpstamp = bb.getInt();
314 | senderPkts = bb.getInt();
315 | senderOcts = bb.getInt();
316 | reports = new ArrayList();
317 | for (int i = 0; i < rc; i++) {
318 | ReportBlock rblock = new ReportBlock(bb);
319 | reports.add(rblock);
320 | }
321 | }
322 |
323 | private SenderReport() {
324 | reports = new ArrayList();
325 | pt = SR;
326 | }
327 |
328 | public String toString() {
329 | String ret = "RTCP SR: ssrc=" + ssrc + " ntpstamp=" + ntpstamp + " rtpstamp=" + rtpstamp + " senderPkts=" + senderPkts + " senderOcts=" + senderOcts;
330 | for (ReportBlock b : reports) {
331 | ret += "\n\t" + b.toString();
332 | }
333 | return ret;
334 | }
335 |
336 | }
337 |
338 | public static class ReceiverReport extends RTCP {
339 |
340 | /*
341 | 0 1 2 3
342 | 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
343 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
344 | header |V=2|P| RC | PT=RR=201 | length |
345 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
346 | | SSRC of packet sender |
347 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
348 | report | SSRC_1 (SSRC of first source) |
349 | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
350 | 1 | fraction lost | cumulative number of packets lost |
351 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
352 | | extended highest sequence number received |
353 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
354 | | interarrival jitter |
355 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
356 | | last SR (LSR) |
357 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
358 | | delay since last SR (DLSR) |
359 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
360 | report | SSRC_2 (SSRC of second source) |
361 | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
362 | 2 : ... :
363 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
364 | | profile-specific extensions |
365 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
366 |
367 | */
368 | long ssrc;
369 | ArrayList reports;
370 |
371 | public ReceiverReport(ByteBuffer bb, int rc, int length) throws InvalidRTCPPacketException {
372 | int expected = 1 + (6 * rc);
373 | Log.verb("length expected=" + expected + " got length=" + length + " rc = " + rc);
374 |
375 | if (length != expected) {
376 | throw new InvalidRTCPPacketException("length mismatch expected=" + expected + " got length=" + length);
377 | }
378 |
379 | ssrc = bb.getInt();
380 | reports = new ArrayList();
381 | for (int i = 0; i < rc; i++) {
382 | ReportBlock rblock = new ReportBlock(bb);
383 | reports.add(rblock);
384 | }
385 | }
386 |
387 | @Override
388 | public void addBody(ByteBuffer bb) {
389 | super.addBody(bb);
390 | }
391 |
392 | @Override
393 | int getRC() {
394 | return reports.size();
395 | }
396 |
397 | @Override
398 | int estimateBodyLength() {
399 | return 6 + (6 * reports.size());
400 | }
401 |
402 | private ReceiverReport() {
403 | reports = new ArrayList();
404 | pt = RR;
405 | }
406 |
407 | public ReportBlock[] getReports() {
408 | ReportBlock[] ret = new ReportBlock[reports.size()];
409 | for (int i = 0; i < reports.size(); i++) {
410 | ret[i] = reports.get(i);
411 | }
412 | return ret;
413 | }
414 |
415 | public String toString() {
416 | String ret = "RTCP RR: ssrc=" + ssrc;
417 | for (ReportBlock b : reports) {
418 | ret += "\n\t" + b.toString();
419 | }
420 | return ret;
421 | }
422 | }
423 |
424 | public RTCP() {
425 | }
426 |
427 | public static class SDES extends RTCP {
428 |
429 | public SDES(ByteBuffer bb, int rc, int length) {
430 | }
431 | }
432 |
433 | public static class BYE extends RTCP {
434 |
435 | public BYE(ByteBuffer bb, int rc, int length) {
436 | }
437 | }
438 |
439 | public static class FB extends RTCP {
440 |
441 | protected long sssrc;
442 | protected long mssrc;
443 | protected int fmt;
444 | protected byte[] fci;
445 |
446 | /*
447 | Figure 3:
448 |
449 | 0 1 2 3
450 | 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
451 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
452 | |V=2|P| FMT | PT | length |
453 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
454 | | SSRC of packet sender |
455 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
456 | | SSRC of media source |
457 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
458 | : Feedback Control Information (FCI) :
459 | : :
460 | */
461 | public FB(ByteBuffer bb, int rc, int length) throws InvalidRTCPPacketException {
462 | if (length < 2) {
463 | throw new InvalidRTCPPacketException("length expected at least 2 got " + length);
464 | }
465 | sssrc = bb.getInt();
466 | mssrc = bb.getInt();
467 | fmt = rc;
468 | fci = new byte[(length - 2) * 4];
469 | bb.get(fci);
470 | }
471 |
472 | protected FB() {
473 | super();
474 | }
475 | @Override
476 | public void addBody(ByteBuffer bb) {
477 | super.addBody(bb);
478 | bb.putInt((int)mssrc);
479 | bb.put(fci);
480 | }
481 |
482 | public long getSssrc() {
483 | return sssrc;
484 | }
485 |
486 | public long getMssrc() {
487 | return mssrc;
488 | }
489 |
490 | public int getFmt() {
491 | return fmt;
492 | }
493 | @Override
494 | public int getRC() {
495 | return fmt;
496 | }
497 | public byte[] getFci() {
498 | return fci;
499 | }
500 |
501 | @Override
502 | public String toString() {
503 | String ret = "RTCP FB: sssrc=" + sssrc + " mssrc=" + mssrc + " fmt=" + fmt + " fci length=" + fci.length;
504 | return ret;
505 | }
506 |
507 |
508 | @Override
509 | int estimateBodyLength() {
510 | return (fci.length/4) + 2;
511 | }
512 | /**
513 | * @param sssrc the sssrc to set
514 | */
515 | public void setSssrc(long sssrc) {
516 | this.sssrc = sssrc;
517 | }
518 |
519 | /**
520 | * @param mssrc the mssrc to set
521 | */
522 | public void setMssrc(long mssrc) {
523 | this.mssrc = mssrc;
524 | }
525 |
526 | /**
527 | * @param fmt the fmt to set
528 | */
529 | public void setFmt(int fmt) {
530 | this.fmt = fmt;
531 | }
532 |
533 | /**
534 | * @param fci the fci to set
535 | */
536 | public void setFci(byte[] fci) {
537 | this.fci = fci;
538 | }
539 | }
540 |
541 | public static class PSFB extends FB {
542 |
543 | public PSFB(ByteBuffer bb, int rc, int length) throws InvalidRTCPPacketException {
544 | super(bb, rc, length);
545 | }
546 |
547 | protected PSFB() {
548 | super();
549 | pt = PSFB;
550 | }
551 |
552 | public String toString() {
553 | String ret = "RTCP PSFB: sssrc=" + sssrc + " mssrc=" + mssrc + " fmt=" + fmt + " fci length=" + fci.length;
554 | return ret;
555 | }
556 |
557 | @Override
558 | public void addBody(ByteBuffer bb) {
559 | super.addBody(bb);
560 | }
561 |
562 | @Override
563 | int estimateBodyLength() {
564 | return super.estimateBodyLength();
565 | }
566 | }
567 |
568 | public static class RTPFB extends FB {
569 |
570 | public RTPFB(ByteBuffer bb, int rc, int length) throws InvalidRTCPPacketException {
571 | super(bb, rc, length);
572 | }
573 |
574 | public String toString() {
575 | String ret = "RTCP RTPFB: sssrc=" + sssrc + " mssrc=" + mssrc + " fmt=" + fmt + " fci length=" + fci.length;
576 | return ret;
577 | }
578 |
579 | public List getSeqList() {
580 | ArrayList ret = new ArrayList();
581 | ByteBuffer bb = ByteBuffer.wrap(fci);
582 | long lost = (long) bb.getChar();
583 | ret.add(lost);
584 | long bits = (long) bb.getChar();
585 | for (int i = 0; i < 16; i++) {
586 | long bit = bits & 0x1;
587 | if (bit != 0) {
588 | ret.add(lost + i + 1);
589 | }
590 | bits = bits >> 1;
591 | }
592 | return ret;
593 | }
594 | }
595 |
596 | public class ReportBlock {
597 |
598 | /*
599 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
600 | report | SSRC_1 (SSRC of first source) |
601 | block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
602 | 1 | fraction lost | cumulative number of packets lost |
603 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
604 | | extended highest sequence number received |
605 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
606 | | interarrival jitter |
607 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
608 | | last SR (LSR) |
609 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
610 | | delay since last SR (DLSR) |
611 | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
612 | */
613 | long ssrc;
614 | int frac;
615 | int cumulost;
616 | long highestSeqRcvd;
617 | long iaJitter;
618 | long lsr;
619 | long dlsr;
620 |
621 | ReportBlock(ByteBuffer bb) {
622 | ssrc = bb.getInt();
623 | char c3 = bb.getChar();
624 | char c4 = bb.getChar();
625 | frac = c3 & 0xff00 >>> 8;
626 | cumulost = c4 + ((c3 & 0xff) << 16);
627 | highestSeqRcvd = bb.getInt();
628 | iaJitter = bb.getInt();
629 | lsr = bb.getInt();
630 | dlsr = bb.getInt();
631 | }
632 |
633 | public long getSsrc() {
634 | return ssrc;
635 | }
636 |
637 | public int getFrac() {
638 | return frac;
639 | }
640 |
641 | public int getCumulost() {
642 | return cumulost;
643 | }
644 |
645 | public long getHighestSeqRcvd() {
646 | return highestSeqRcvd;
647 | }
648 |
649 | public long getIaJitter() {
650 | return iaJitter;
651 | }
652 |
653 | public long getLsr() {
654 | return lsr;
655 | }
656 |
657 | public long getDlsr() {
658 | return dlsr;
659 | }
660 |
661 | public String toString() {
662 | return "ReportBlock for ssrc=" + ssrc + " frac=" + frac + " cumulost=" + cumulost + " highestSeqRcvd=" + highestSeqRcvd + " iaJitter=" + iaJitter + " lsr=" + lsr + " dlsr=" + dlsr;
663 | }
664 |
665 | private void addBody(ByteBuffer bb) {
666 | throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
667 | }
668 |
669 | }
670 |
671 | }
672 |
--------------------------------------------------------------------------------
/src/main/java/com/phono/srtplight/SRTPSecContext.java:
--------------------------------------------------------------------------------
1 | package com.phono.srtplight;
2 |
3 | /*
4 | * Copyright 2011 Voxeo Corp.
5 | *
6 | * Licensed under the Apache License, Version 2.0 (the "License");
7 | * you may not use this file except in compliance with the License.
8 | * You may obtain a copy of the License at
9 | *
10 | * http://www.apache.org/licenses/LICENSE-2.0
11 | *
12 | * Unless required by applicable law or agreed to in writing, software
13 | * distributed under the License is distributed on an "AS IS" BASIS,
14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | * See the License for the specific language governing permissions and
16 | * limitations under the License.
17 | *
18 | */
19 |
20 |
21 |
22 | import biz.source_code.Base64Coder;
23 | import java.nio.Buffer;
24 | import java.nio.ByteBuffer;
25 | import java.security.GeneralSecurityException;
26 | import java.util.Properties;
27 | import javax.crypto.Cipher;
28 | import javax.crypto.Mac;
29 | import javax.crypto.SecretKey;
30 | import javax.crypto.spec.SecretKeySpec;
31 |
32 | public class SRTPSecContext {
33 |
34 | static int KEYLEN = 16; // 128 bit
35 | static int MACKEYLEN = 20;
36 | static int BLOCKSZ = 16; // also but possibly different
37 | private Cipher _anAES;
38 | protected byte[] _sessionKey = new byte[0];
39 | ByteBuffer _masterSalt;
40 | protected byte[] _cipherSalt;
41 | protected byte[] _sessionAuth;
42 | private int _tag;
43 | private int _authTail;
44 | byte[] _masterKey;
45 | private int _mki;
46 | private int _mkiLen;
47 | private int _kdr;
48 | private int _keyLife;
49 | private boolean _doAuth;
50 | private boolean _doCrypt;
51 | private boolean _in;
52 | protected String _dirn;
53 |
54 | SRTPSecContext(boolean in) {
55 | _in = in;
56 | _dirn = in ? "in " : "out ";
57 | }
58 |
59 | SRTPSecContext() {
60 | this(false);
61 | }
62 |
63 | static byte[] saba(short[] s) {
64 | byte[] r = new byte[s.length];
65 | for (int i = 0; i < r.length; i++) {
66 | r[i] = (byte) s[i];
67 | }
68 | return r;
69 | }
70 |
71 | public int getAuthTail() {
72 | return _authTail;
73 | }
74 |
75 | public static void main(String argv[]) {
76 | SRTPSecContext s = new SRTPSecContext();
77 | try {
78 | System.out.println("test key derivation");
79 | s.testDeriv();
80 | s.testHmac();
81 | } catch (GeneralSecurityException ex) {
82 | System.out.println("Failed ");
83 | ex.printStackTrace();
84 | }
85 |
86 | }
87 |
88 | private void testResult(byte[] a, byte[] b) throws GeneralSecurityException {
89 | if (a.length != b.length) {
90 | throw new GeneralSecurityException("lengths dont match " + a.length + " != " + b.length);
91 | }
92 | for (int i = 0; i < a.length; i++) {
93 | if (a[i] != b[i]) {
94 | throw new GeneralSecurityException("values " + i + " dont match " + a[i] + " != " + b[i]);
95 | }
96 | }
97 | }
98 |
99 | private void testHmac() throws GeneralSecurityException {
100 | short hmac_test_case_0_keyS[] = {
101 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
102 | 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
103 | 0x0b, 0x0b, 0x0b, 0x0b
104 | };
105 |
106 | short hmac_test_case_0_dataS[] = {
107 | 0x48, 0x69, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65 /* "Hi There" */};
108 |
109 | short hmac_test_case_0_tagS[] = {
110 | 0xb6, 0x17, 0x31, 0x86, 0x55, 0x05, 0x72, 0x64,
111 | 0xe2, 0x8b, 0xc0, 0xb6, 0xfb, 0x37, 0x8c, 0x8e,
112 | 0xf1, 0x46, 0xbe, 0x00
113 | };
114 | System.err.println("test hmac ");
115 |
116 | byte[] hmac_test_case_0_key = saba(hmac_test_case_0_keyS);
117 | byte[] hmac_test_case_0_data = saba(hmac_test_case_0_dataS);
118 | byte[] hmac_test_case_0_tag = saba(hmac_test_case_0_tagS);
119 | this._sessionAuth = hmac_test_case_0_key;
120 | Mac m = this.getAuthMac();
121 | byte[] res = m.doFinal(hmac_test_case_0_data);
122 | for (int i = 0; i < res.length; i++) {
123 | if (res[i] != hmac_test_case_0_tag[i]) {
124 | throw new GeneralSecurityException("auth mac failed selftest ");
125 | }
126 | }
127 | }
128 |
129 | private void testDeriv() throws GeneralSecurityException {
130 | short[] masterkeyS = {0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39};
131 | short[] mastersaltS = {0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6};
132 | short[] plaintextS = {0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 0x00, 0x00};
133 | short[] reskeyS = {0xC6, 0x1E, 0x7A, 0x93, 0x74, 0x4F, 0x39, 0xEE, 0x10, 0x73, 0x4A, 0xFE, 0x3F, 0xF7, 0xA0, 0x87};
134 | short[] csaltS = {0x30, 0xCB, 0xBC, 0x08, 0x86, 0x3D, 0x8C, 0x85, 0xD4, 0x9D, 0xB3, 0x4A, 0x9A, 0xE1, 0x7A, 0xC6};
135 | short[] authKeyS = {0xCE, 0xBE, 0x32, 0x1F, 0x6F, 0xF7, 0x71, 0x6B, 0x6F, 0xD4, 0xAB, 0x49, 0xAF, 0x25, 0x6A, 0x15, 0x6D, 0x38, 0xBA, 0xA4};
136 | _masterKey = saba(masterkeyS);
137 | byte mastersalt[] = saba(mastersaltS);
138 | _masterSalt = ByteBuffer.allocate(16);
139 | _masterSalt.put(mastersalt);
140 | byte plaintext[] = saba(plaintextS);
141 | byte reskey[] = saba(reskeyS);
142 | byte csalt[] = saba(csaltS);
143 | byte authkey[] = saba(authKeyS);
144 | deriveKeys(0, 0);
145 | System.err.println("test session key");
146 | testResult(_sessionKey, reskey);
147 |
148 | System.err.println("test session salt");
149 | testResult(_cipherSalt, csalt);
150 |
151 |
152 | System.err.println("test auth key");
153 | testResult(_sessionAuth, authkey);
154 |
155 | }
156 |
157 | static void bbxor(ByteBuffer l, ByteBuffer r) {
158 | int num = Math.min(l.capacity(), r.capacity());
159 | for (int i = 0; i < num; i++) {
160 | byte a = (byte) (r.get(i) ^ l.get(i));
161 | l.put(i, a);
162 | }
163 | }
164 |
165 | ByteBuffer cloneByteBuffer(ByteBuffer orig) {
166 | ByteBuffer ret = ByteBuffer.allocate(orig.capacity());
167 | ret.put(orig.array(), 0, orig.capacity());
168 | return ret;
169 | }
170 |
171 | void deriveKeys(long index) throws GeneralSecurityException {
172 | if (haveKeys() && (_kdr != 0) && ((index % _kdr) == 0)) {
173 | deriveKeys(index, _kdr);
174 | } else {
175 | if (!haveKeys()) {
176 | deriveKeys(0L, 0);
177 | }
178 | }
179 | }
180 | protected void deriveKeys(long index, int kdr) throws GeneralSecurityException {
181 | deriveKeys(index,kdr,0,2,1);
182 | }
183 | protected void deriveKeys(long index, int kdr,int sess, int salt, int auth) throws GeneralSecurityException {
184 | /*
185 | The input block for
186 | AES-CM is generated by exclusive-oring the master salt with the
187 | concatenation of the encryption key label 0x00 with (index DIV kdr),
188 | then padding on the right with two null octets
189 | *
190 | */
191 |
192 | /*
193 | * The resulting value
194 | is then AES-CM- encrypted using the master key to get the cipher key.
195 | */
196 | byte label = (byte)sess;
197 | ByteBuffer myinpblk = cloneByteBuffer(_masterSalt);
198 | ByteBuffer lex = ByteBuffer.allocate(BLOCKSZ);
199 | long idivk = (kdr == 0) ? 0 : index / kdr;
200 | lex.putLong(6, idivk);
201 | lex.put(7, label);
202 | bbxor(myinpblk, lex);
203 | _sessionKey = getKeyBytes(myinpblk, KEYLEN);
204 |
205 | label = (byte)salt;
206 | myinpblk = cloneByteBuffer(_masterSalt);
207 | lex = ByteBuffer.allocate(BLOCKSZ);
208 | idivk = (kdr == 0) ? 0 : index / kdr;
209 | lex.putLong(6, idivk);
210 | lex.put(7, label);
211 | bbxor(myinpblk, lex);
212 | _cipherSalt = getKeyBytes(myinpblk, KEYLEN);
213 | // now zap the tail bytes
214 |
215 | _cipherSalt[14] = 0;
216 | _cipherSalt[15] = 0;
217 |
218 |
219 |
220 |
221 | label = (byte)auth;
222 | myinpblk = cloneByteBuffer(_masterSalt);
223 | lex = ByteBuffer.allocate(BLOCKSZ);
224 | idivk = (kdr == 0) ? 0 : index / kdr;
225 | lex.putLong(6, idivk);
226 | lex.put(7, label);
227 |
228 | bbxor(myinpblk, lex);
229 |
230 | _sessionAuth = getKeyBytes(myinpblk, MACKEYLEN);
231 | if (Log.getLevel() > Log.DEBUG) {
232 | Log.verb(_dirn + "Derive keys for index = " + index + " kdr =" + kdr);
233 | Log.verb(_dirn + "Session Key = " + SRTPProtocolImpl.getHex(_sessionKey));
234 | Log.verb(_dirn + "CipherSalt Key = " + SRTPProtocolImpl.getHex(_cipherSalt));
235 | Log.verb(_dirn + "Auth Key = " + SRTPProtocolImpl.getHex(_sessionAuth));
236 | }
237 | }
238 |
239 | /*
240 | *
241 | * repeatedly encrypt the salt - and a counter
242 | * resulting in a pseudo random stream of bytes of
243 | * the required length.
244 | * (but predictable if you have the key and salt)
245 | */
246 | void getCypherStreamBytes(Cipher aes, ByteBuffer asalt, ByteBuffer stream) throws GeneralSecurityException {
247 | int blocksz = 128 / 8;
248 | ByteBuffer slop;
249 |
250 | int toget = stream.capacity() - 32; // ensuring that there are 16bytes of overhead
251 | ((Buffer)stream).position(0);
252 | int blks = toget / blocksz;
253 | char bno = 0;
254 | // repeatedly encrypt the salt (and some pepper)
255 | // where the pepper is 16bit unsigned int at the end of the 128 bit int
256 | // the pepper increments once with each round.
257 | // tedious issue where aes won't crypt unless there are 32 bytes to go in stream.
258 |
259 | for (; bno < blks; bno++) {
260 | asalt.putChar(14, bno);
261 | ((Buffer)asalt).position(0);
262 | aes.update(asalt, stream);
263 | }
264 | // now finish off the < 128 bits at the end.
265 | asalt.putChar(14, bno);
266 | ((Buffer)asalt).position(0);
267 | aes.doFinal(asalt, stream);
268 | }
269 |
270 | protected byte[] getKeyBytes(ByteBuffer inp, int want) throws GeneralSecurityException {
271 | int blocksz = 128 / 8;
272 | byte[] ret = null;
273 | if (want == blocksz) {
274 | // odd special case where crypto wants bigger bucket
275 | ByteBuffer slop = ByteBuffer.allocate(want * 2);
276 | inp.putChar(14, (char) 0);
277 | ((Buffer)inp).position(0);
278 | _anAES.doFinal(inp, slop);
279 | ret = new byte[want];
280 | ((Buffer)slop).position(0);
281 | slop.get(ret);
282 | } else {
283 | ByteBuffer stream = ByteBuffer.allocate(want + 32);
284 | getCypherStreamBytes(_anAES, inp, stream);
285 | ret = new byte[want];
286 | System.arraycopy(stream.array(), 0, ret, 0, want);
287 | }
288 | return ret;
289 | }
290 |
291 | Mac getAuthMac() throws GeneralSecurityException {
292 | if (Log.getLevel() > Log.DEBUG) {
293 | Log.verb(_dirn + "getting Hmac key = " + SRTPProtocolImpl.getHex(_sessionAuth));
294 | }
295 | SecretKey key = new SecretKeySpec(this._sessionAuth, "HmacSHA1");
296 | Mac m = Mac.getInstance("HmacSHA1");
297 | m.init(key);
298 | return m;
299 | }
300 |
301 | void decipher(ByteBuffer in, ByteBuffer out, ByteBuffer pepper) throws GeneralSecurityException {
302 |
303 |
304 | SecretKeySpec keyp = new SecretKeySpec(_sessionKey, "AES");
305 |
306 | Cipher aes = Cipher.getInstance("AES"); // perhaps AES/None/NoPadding or ""
307 | aes.init(Cipher.ENCRYPT_MODE, keyp);
308 | if (Log.getLevel() > Log.DEBUG) {
309 | Log.verb(_dirn + "decipher: key =" + SRTPProtocolImpl.getHex(_sessionKey));
310 | }
311 |
312 | /*
313 | where the 128-bit integer value IV SHALL be defined by the SSRC, the
314 | SRTP packet index i, and the SRTP session salting key k_s, as below.
315 |
316 | IV = (k_s * 2^16) XOR (SSRC * 2^64) XOR (i * 2^16)
317 | *
318 | */
319 | ByteBuffer csalt = ByteBuffer.wrap(_cipherSalt);
320 | if (Log.getLevel() > Log.DEBUG) {
321 | Log.verb(_dirn + "decipher: salt =" + SRTPProtocolImpl.getHex(csalt.array()));
322 | }
323 | if (Log.getLevel() > Log.DEBUG) {
324 | Log.verb(_dirn + "decipher: pepper =" + SRTPProtocolImpl.getHex(pepper.array()));
325 | }
326 |
327 |
328 | bbxor(pepper, csalt);
329 | if (Log.getLevel() > Log.DEBUG) {
330 | Log.verb(_dirn + "decipher: IV =" + SRTPProtocolImpl.getHex(pepper.array()));
331 | }
332 |
333 | if (Log.getLevel() > Log.DEBUG) {
334 | Log.verb(_dirn + "decipher: in =" + SRTPProtocolImpl.getHex(in.array()));
335 | }
336 | getCypherStreamBytes(aes, pepper, out);
337 | if (Log.getLevel() > Log.DEBUG) {
338 | Log.verb(_dirn + "decipher: stream =" + SRTPProtocolImpl.getHex(out.array()));
339 | }
340 |
341 | bbxor(out, in);
342 | if (Log.getLevel() > Log.DEBUG) {
343 | Log.verb(_dirn + "decipher: xor =" + SRTPProtocolImpl.getHex(out.array()));
344 | }
345 |
346 | }
347 |
348 | boolean haveKeys() {
349 | return ((_sessionAuth != null) && (_sessionKey != null) && (this._cipherSalt != null));
350 | }
351 |
352 | static String stripQ(String s) {
353 | String d = s;
354 | if ((s != null) && (s.indexOf("'") == 0)) {
355 | int lq = s.lastIndexOf("'");
356 |
357 | d = s.substring(1, lq);
358 |
359 | }
360 |
361 | return d;
362 | }
363 |
364 | void parseCryptoProps(Properties cryptoProps) throws GeneralSecurityException {
365 | /*
366 | * required='1' crypto-suite='AES_CM_128_HMAC_SHA1_80' key-params='inline:WVNfX19zZW1jdGwgKCkgewkyMjA7fQp9CnVubGVz' session-params='KDR=1 UNENCRYPTED_SRTCP' tag='1'
367 | */
368 | String crypto_suite = stripQ(cryptoProps.getProperty("crypto-suite"));
369 | String key_params = stripQ(cryptoProps.getProperty("key-params"));
370 | String session_params = stripQ(cryptoProps.getProperty("session-params"));
371 | String tag = stripQ(cryptoProps.getProperty("tag"));
372 | if (crypto_suite.equals("AES_CM_128_HMAC_SHA1_80")) {
373 | _authTail = 10;
374 | } else if (crypto_suite.equals("AES_CM_128_HMAC_SHA1_32")) {
375 | _authTail = 4;
376 | } else {
377 | throw new GeneralSecurityException("Unsupported crypto suite " + crypto_suite);
378 | }
379 | if ((key_params != null) && (key_params.startsWith("inline:"))) {
380 | // "inline:" ["|" lifetime] ["|" MKI ":" length]
381 | String ks = key_params.substring("inline:".length());
382 | String mkil = null;
383 | String life = null;
384 | if (ks.indexOf("|") > 0) {
385 | String[] kbits = ks.split("\\|");
386 | ks = kbits[0];
387 |
388 | // now deal with the ugly optional syntax
389 |
390 | if ((kbits.length > 1) && kbits[1].contains(":")) {
391 | mkil = kbits[1];
392 | } else {
393 | if (kbits.length > 1) {
394 | life = kbits[1];
395 | }
396 | if ((kbits.length > 2) && kbits[1].contains(":")) {
397 | mkil = kbits[2];
398 | }
399 | }
400 | }
401 | //Log.debug(_dirn + "key n salt =" + ks);
402 | byte mks[] = Base64Coder.decode(ks);
403 | if (mks.length != 30) {
404 | throw new GeneralSecurityException("Master ket/salt too short - expecting 30 got" + mks.length);
405 | }
406 | _masterKey = new byte[16];
407 | System.arraycopy(mks, 0, _masterKey, 0, _masterKey.length);
408 | SecretKeySpec keyp = new SecretKeySpec(_masterKey, "AES");
409 | _anAES = Cipher.getInstance("AES"); // perhaps AES/None/NoPadding or ""
410 | _anAES.init(Cipher.ENCRYPT_MODE, keyp);
411 | _masterSalt = ByteBuffer.allocate(KEYLEN);
412 | _masterSalt.put(mks, _masterKey.length, 14);
413 |
414 |
415 | if (mkil != null) {
416 | String[] mbits = mkil.split("\\:");
417 | if (mbits.length == 2) {
418 | _mki = Integer.parseInt(mbits[0]);
419 | _mkiLen = Integer.parseInt(mbits[1]);
420 | Log.warn(_dirn + "MKI:length defined");
421 | } else {
422 | throw new GeneralSecurityException("Expecting MKI:length");
423 | }
424 | }
425 | if (life != null) {
426 | if (life.contains("^")) {
427 | String klb[] = life.split("\\^");
428 | if (klb.length == 2) {
429 | int f = Integer.parseInt(klb[0]);
430 | int p = Integer.parseInt(klb[1]);
431 | if (f == 2) {
432 | _keyLife = 1 << p;
433 | } else {
434 | _keyLife = (int) Math.pow(f, p);
435 | }
436 | }
437 | } else {
438 | _keyLife = Integer.parseInt(life);
439 | }
440 | Log.warn("Key life is " + _keyLife);
441 |
442 | }
443 | } else {
444 | throw new GeneralSecurityException("Missing inline key/salt in SDP");
445 | }
446 |
447 | // now do something with the session params and the tag.
448 | if (session_params != null) {
449 | String[] sbits = session_params.split(" ");
450 | for (int i = 0; i < sbits.length; i++) {
451 | Log.debug(_dirn + "SDP session param " + sbits[i]);
452 |
453 | if (sbits[i].startsWith("KDR=")) {
454 | //The SRTP Key Derivation Rate is the rate at which a
455 | // pseudo-random function is applied to a master key.
456 | String kdrs = sbits[i].substring("KDR=".length());
457 | _kdr = Integer.parseInt(kdrs);
458 | Log.debug(_dirn + "setting kdr to " + _kdr);
459 | if (_kdr != 0) {
460 | Log.debug(_dirn + "Note: Non-zero kdr");
461 | }
462 | }
463 | if ("UNENCRYPTED_SRTP".equals(sbits[i])) {
464 | // - : SRTP messages are not encrypted.
465 | _doCrypt = false;
466 | Log.warn(_dirn + "Switching off encryption - is this what you wanted ?");
467 | }
468 | if ("UNENCRYPTED_SRTCP".equals(sbits[i])) {
469 | //SRTCP messages are not encrypted.
470 | Log.debug(_dirn + "No support for SRTCP (yet) so SDP setting ignored");
471 | }
472 | if ("UNAUTHENTICATED_SRTP".equals(sbits[i])) {
473 | _doAuth = false;
474 | }
475 | //: SRTP messages are not authenticated.
476 | if ("FEC_ORDER".equals(sbits[i])) {
477 | //Order of forward error correction (FEC)
478 | // relative to SRTP services.
479 | }
480 | if ("FEC_KEY".equals(sbits[i])) {
481 | //Master Key for FEC when the FEC stream is sent
482 | // to a separate address and/or port.
483 | }
484 | if ("WSH".equals(sbits[i])) {
485 | //Window Size Hint.
486 | }
487 | }
488 | }
489 | if (tag != null) {
490 | _tag = Integer.parseInt(tag);
491 | Log.debug(_dirn + "SDP tag = " + _tag);
492 | }
493 | }
494 | /*
495 | 4.3.1. Key Derivation Algorithm
496 |
497 | Regardless of the encryption or message authentication transform that
498 | is employed (it may be an SRTP pre-defined transform or newly
499 | introduced according to Section 6), interoperable SRTP
500 | implementations MUST use the SRTP key derivation to generate session
501 | keys. Once the key derivation rate is properly signaled at the start
502 | of the session, there is no need for extra communication between the
503 | parties that use SRTP key derivation.
504 |
505 | packet index ---+
506 | |
507 | v
508 | +-----------+ master +--------+ session encr_key
509 | | ext | key | |---------->
510 | | key mgmt |-------->| key | session auth_key
511 | | (optional | | deriv |---------->
512 | | rekey) |-------->| | session salt_key
513 | | | master | |---------->
514 | +-----------+ salt +--------+
515 |
516 | Figure 5: SRTP key derivation.
517 |
518 | At least one initial key derivation SHALL be performed by SRTP, i.e.,
519 | the first key derivation is REQUIRED. Further applications of the
520 | key derivation MAY be performed, according to the
521 | "key_derivation_rate" value in the cryptographic context. The key
522 | derivation function SHALL initially be invoked before the first
523 | packet and then, when r > 0, a key derivation is performed whenever
524 | index mod r equals zero. This can be thought of as "refreshing" the
525 | session keys. The value of "key_derivation_rate" MUST be kept fixed
526 | for the lifetime of the associated master key.
527 |
528 | Interoperable SRTP implementations MAY also derive session salting
529 | keys for encryption transforms, as is done in both of the pre-
530 | defined transforms.
531 |
532 | Let m and n be positive integers. A pseudo-random function family is
533 | a set of keyed functions {PRF_n(k,x)} such that for the (secret)
534 | random key k, given m-bit x, PRF_n(k,x) is an n-bit string,
535 | computationally indistinguishable from random n-bit strings, see
536 | [HAC]. For the purpose of key derivation in SRTP, a secure PRF with
537 | m = 128 (or more) MUST be used, and a default PRF transform is
538 | defined in Section 4.3.3.
539 |
540 | Let "a DIV t" denote integer division of a by t, rounded down, and
541 | with the convention that "a DIV 0 = 0" for all a. We also make the
542 | convention of treating "a DIV t" as a bit string of the same length
543 | as a, and thus "a DIV t" will in general have leading zeros.
544 |
545 | Key derivation SHALL be defined as follows in terms of