001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020
021package org.apache.mina.filter.errorgenerating;
022
023import java.util.Random;
024
025import org.apache.mina.core.buffer.IoBuffer;
026import org.apache.mina.core.filterchain.IoFilter;
027import org.apache.mina.core.filterchain.IoFilterAdapter;
028import org.apache.mina.core.session.IoSession;
029import org.apache.mina.core.write.DefaultWriteRequest;
030import org.apache.mina.core.write.WriteRequest;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034/**
035 * An {@link IoFilter} implementation generating random bytes and PDU modification in
036 * your communication streams.
037 * It's quite simple to use :
038 * <code>ErrorGeneratingFilter egf = new ErrorGeneratingFilter();</code>
039 * For activate the change of some bytes in your {@link IoBuffer}, for a probability of 200 out
040 * of 1000 {@link IoBuffer} processed :
041 * <code>egf.setChangeByteProbability(200);</code>
042 * For activate the insertion of some bytes in your {@link IoBuffer}, for a
043 * probability of 200 out of 1000 :
044 * <code>egf.setInsertByteProbability(200);</code>
045 * And for the removing of some bytes :
046 * <code>egf.setRemoveByteProbability(200);</code>
047 * You can activate the error generation for write or read with the
048 * following methods :
049 * <code>egf.setManipulateReads(true);
050 * egf.setManipulateWrites(true); </code>
051 * 
052 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
053 * @org.apache.xbean.XBean
054 */
055public class ErrorGeneratingFilter extends IoFilterAdapter {
056    private int removeByteProbability = 0;
057
058    private int insertByteProbability = 0;
059
060    private int changeByteProbability = 0;
061
062    private int removePduProbability = 0;
063
064    private int duplicatePduProbability = 0;
065
066    private int resendPduLasterProbability = 0;
067
068    private int maxInsertByte = 10;
069
070    private boolean manipulateWrites = false;
071
072    private boolean manipulateReads = false;
073
074    private Random rng = new Random();
075
076    final private Logger logger = LoggerFactory.getLogger(ErrorGeneratingFilter.class);
077
078    @Override
079    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
080        if (manipulateWrites) {
081            // manipulate bytes
082            if (writeRequest.getMessage() instanceof IoBuffer) {
083                manipulateIoBuffer(session, (IoBuffer) writeRequest.getMessage());
084                IoBuffer buffer = insertBytesToNewIoBuffer(session, (IoBuffer) writeRequest.getMessage());
085                if (buffer != null) {
086                    writeRequest = new DefaultWriteRequest(buffer, writeRequest.getFuture(),
087                            writeRequest.getDestination());
088                }
089                // manipulate PDU
090            } else {
091                if (duplicatePduProbability > rng.nextInt()) {
092                    nextFilter.filterWrite(session, writeRequest);
093                }
094
095                if (resendPduLasterProbability > rng.nextInt()) {
096                    // store it somewhere and trigger a write execution for
097                    // later
098                    // TODO
099                }
100                if (removePduProbability > rng.nextInt()) {
101                    return;
102                }
103            }
104        }
105        nextFilter.filterWrite(session, writeRequest);
106    }
107
108    @Override
109    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
110        if (manipulateReads) {
111            if (message instanceof IoBuffer) {
112                // manipulate bytes
113                manipulateIoBuffer(session, (IoBuffer) message);
114                IoBuffer buffer = insertBytesToNewIoBuffer(session, (IoBuffer) message);
115                if (buffer != null) {
116                    message = buffer;
117                }
118            } else {
119                // manipulate PDU
120                // TODO
121            }
122        }
123        nextFilter.messageReceived(session, message);
124    }
125
126    private IoBuffer insertBytesToNewIoBuffer(IoSession session, IoBuffer buffer) {
127        if (insertByteProbability > rng.nextInt(1000)) {
128            logger.info(buffer.getHexDump());
129            // where to insert bytes ?
130            int pos = rng.nextInt(buffer.remaining()) - 1;
131
132            // how many byte to insert ?
133            int count = rng.nextInt(maxInsertByte - 1) + 1;
134
135            IoBuffer newBuff = IoBuffer.allocate(buffer.remaining() + count);
136            for (int i = 0; i < pos; i++)
137                newBuff.put(buffer.get());
138            for (int i = 0; i < count; i++) {
139                newBuff.put((byte) (rng.nextInt(256)));
140            }
141            while (buffer.remaining() > 0) {
142                newBuff.put(buffer.get());
143            }
144            newBuff.flip();
145
146            logger.info("Inserted " + count + " bytes.");
147            logger.info(newBuff.getHexDump());
148            return newBuff;
149        }
150        return null;
151    }
152
153    private void manipulateIoBuffer(IoSession session, IoBuffer buffer) {
154        if ((buffer.remaining() > 0) && (removeByteProbability > rng.nextInt(1000))) {
155            logger.info(buffer.getHexDump());
156            // where to remove bytes ?
157            int pos = rng.nextInt(buffer.remaining());
158            // how many byte to remove ?
159            int count = rng.nextInt(buffer.remaining() - pos) + 1;
160            if (count == buffer.remaining())
161                count = buffer.remaining() - 1;
162
163            IoBuffer newBuff = IoBuffer.allocate(buffer.remaining() - count);
164            for (int i = 0; i < pos; i++)
165                newBuff.put(buffer.get());
166
167            buffer.skip(count); // hole
168            while (newBuff.remaining() > 0)
169                newBuff.put(buffer.get());
170            newBuff.flip();
171            // copy the new buffer in the old one
172            buffer.rewind();
173            buffer.put(newBuff);
174            buffer.flip();
175            logger.info("Removed " + count + " bytes at position " + pos + ".");
176            logger.info(buffer.getHexDump());
177        }
178        if ((buffer.remaining() > 0) && (changeByteProbability > rng.nextInt(1000))) {
179            logger.info(buffer.getHexDump());
180            // how many byte to change ?
181            int count = rng.nextInt(buffer.remaining() - 1) + 1;
182
183            byte[] values = new byte[count];
184            rng.nextBytes(values);
185            for (int i = 0; i < values.length; i++) {
186                int pos = rng.nextInt(buffer.remaining());
187                buffer.put(pos, values[i]);
188            }
189            logger.info("Modified " + count + " bytes.");
190            logger.info(buffer.getHexDump());
191        }
192    }
193
194    public int getChangeByteProbability() {
195        return changeByteProbability;
196    }
197
198    /**
199     * Set the probability for the change byte error.
200     * If this probability is &gt; 0 the filter will modify a random number of byte
201     * of the processed {@link IoBuffer}.
202     * @param changeByteProbability probability of modifying an IoBuffer out of 1000 processed {@link IoBuffer} 
203     */
204    public void setChangeByteProbability(int changeByteProbability) {
205        this.changeByteProbability = changeByteProbability;
206    }
207
208    public int getDuplicatePduProbability() {
209        return duplicatePduProbability;
210    }
211
212    /**
213     * not functional ATM
214     * @param duplicatePduProbability The probability for generating duplicated PDU
215     */
216    public void setDuplicatePduProbability(int duplicatePduProbability) {
217        this.duplicatePduProbability = duplicatePduProbability;
218    }
219
220    public int getInsertByteProbability() {
221        return insertByteProbability;
222    }
223
224    /**
225     * Set the probability for the insert byte error.
226     * If this probability is &gt; 0 the filter will insert a random number of byte
227     * in the processed {@link IoBuffer}.
228     * @param insertByteProbability probability of inserting in IoBuffer out of 1000 processed {@link IoBuffer} 
229     */
230    public void setInsertByteProbability(int insertByteProbability) {
231        this.insertByteProbability = insertByteProbability;
232    }
233
234    public boolean isManipulateReads() {
235        return manipulateReads;
236    }
237
238    /**
239     * Set to true if you want to apply error to the read {@link IoBuffer}
240     * 
241     * @param manipulateReads The number of manipulated reads
242     */
243    public void setManipulateReads(boolean manipulateReads) {
244        this.manipulateReads = manipulateReads;
245    }
246
247    public boolean isManipulateWrites() {
248        return manipulateWrites;
249    }
250
251    /**
252     * Set to true if you want to apply error to the written {@link IoBuffer}
253     * 
254     * @param manipulateWrites The umber of manipulated writes
255     */
256    public void setManipulateWrites(boolean manipulateWrites) {
257        this.manipulateWrites = manipulateWrites;
258    }
259
260    public int getRemoveByteProbability() {
261        return removeByteProbability;
262    }
263
264    /**
265     * Set the probability for the remove byte error.
266     * If this probability is &gt; 0 the filter will remove a random number of byte
267     * in the processed {@link IoBuffer}.
268     * 
269     * @param removeByteProbability probability of modifying an {@link IoBuffer} out of 1000 processed IoBuffer 
270     */
271    public void setRemoveByteProbability(int removeByteProbability) {
272        this.removeByteProbability = removeByteProbability;
273    }
274
275    public int getRemovePduProbability() {
276        return removePduProbability;
277    }
278
279    /**
280     * not functional ATM
281     * @param removePduProbability The PDU removal probability
282     */
283    public void setRemovePduProbability(int removePduProbability) {
284        this.removePduProbability = removePduProbability;
285    }
286
287    public int getResendPduLasterProbability() {
288        return resendPduLasterProbability;
289    }
290
291    /**
292     * not functional ATM
293     * @param resendPduLasterProbability The delay before a resend
294     */
295    public void setResendPduLasterProbability(int resendPduLasterProbability) {
296        this.resendPduLasterProbability = resendPduLasterProbability;
297    }
298
299    public int getMaxInsertByte() {
300        return maxInsertByte;
301    }
302
303    /**
304     * Set the maximum number of byte the filter can insert in a {@link IoBuffer}.
305     * The default value is 10.
306     * @param maxInsertByte maximum bytes inserted in a {@link IoBuffer} 
307     */
308    public void setMaxInsertByte(int maxInsertByte) {
309        this.maxInsertByte = maxInsertByte;
310    }
311}