1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net;
6 
7 import android.os.ConditionVariable;
8 
9 import java.io.IOException;
10 import java.nio.ByteBuffer;
11 import java.nio.channels.ClosedChannelException;
12 import java.util.ArrayList;
13 import java.util.concurrent.Executor;
14 import java.util.concurrent.atomic.AtomicBoolean;
15 
16 /** An UploadDataProvider implementation used in tests. */
17 public class TestUploadDataProvider extends UploadDataProvider {
18     // Indicates whether all success callbacks are synchronous or asynchronous.
19     // Doesn't apply to errors.
20     public enum SuccessCallbackMode {
21         SYNC,
22         ASYNC
23     }
24 
25     // Indicates whether failures should throw exceptions, invoke callbacks
26     // synchronously, or invoke callback asynchronously.
27     public enum FailMode {
28         NONE,
29         THROWN,
30         CALLBACK_SYNC,
31         CALLBACK_ASYNC
32     }
33 
34     private ArrayList<byte[]> mReads = new ArrayList<byte[]>();
35     private final SuccessCallbackMode mSuccessCallbackMode;
36     private final Executor mExecutor;
37 
38     private boolean mChunked;
39 
40     // Index of read to fail on.
41     private int mReadFailIndex = -1;
42     // Indicates how to fail on a read.
43     private FailMode mReadFailMode = FailMode.NONE;
44 
45     private FailMode mRewindFailMode = FailMode.NONE;
46 
47     private FailMode mLengthFailMode = FailMode.NONE;
48 
49     private int mNumReadCalls;
50     private int mNumRewindCalls;
51 
52     private int mNextRead;
53     private boolean mStarted;
54     private boolean mReadPending;
55     private boolean mRewindPending;
56     // Used to ensure there are no read/rewind requests after a failure.
57     private boolean mFailed;
58 
59     private AtomicBoolean mClosed = new AtomicBoolean(false);
60     private ConditionVariable mAwaitingClose = new ConditionVariable(false);
61 
TestUploadDataProvider( SuccessCallbackMode successCallbackMode, final Executor executor)62     public TestUploadDataProvider(
63             SuccessCallbackMode successCallbackMode, final Executor executor) {
64         mSuccessCallbackMode = successCallbackMode;
65         mExecutor = executor;
66     }
67 
68     // Adds the result to be returned by a successful read request.  The
69     // returned bytes must all fit within the read buffer provided by Cronet.
70     // After a rewind, if there is one, all reads will be repeated.
addRead(byte[] read)71     public void addRead(byte[] read) {
72         if (mStarted) {
73             throw new IllegalStateException("Adding bytes after read");
74         }
75         mReads.add(read);
76     }
77 
setReadFailure(int readFailIndex, FailMode readFailMode)78     public void setReadFailure(int readFailIndex, FailMode readFailMode) {
79         mReadFailIndex = readFailIndex;
80         mReadFailMode = readFailMode;
81     }
82 
setLengthFailure()83     public void setLengthFailure() {
84         mLengthFailMode = FailMode.THROWN;
85     }
86 
setRewindFailure(FailMode rewindFailMode)87     public void setRewindFailure(FailMode rewindFailMode) {
88         mRewindFailMode = rewindFailMode;
89     }
90 
setChunked(boolean chunked)91     public void setChunked(boolean chunked) {
92         mChunked = chunked;
93     }
94 
getNumReadCalls()95     public int getNumReadCalls() {
96         return mNumReadCalls;
97     }
98 
getNumRewindCalls()99     public int getNumRewindCalls() {
100         return mNumRewindCalls;
101     }
102 
103     /** Returns the cumulative length of all data added by calls to addRead. */
104     @Override
getLength()105     public long getLength() throws IOException {
106         if (mClosed.get()) {
107             throw new ClosedChannelException();
108         }
109         if (mLengthFailMode == FailMode.THROWN) {
110             throw new IllegalStateException("Sync length failure");
111         }
112         return getUploadedLength();
113     }
114 
getUploadedLength()115     public long getUploadedLength() {
116         if (mChunked) {
117             return -1;
118         }
119         long length = 0;
120         for (byte[] read : mReads) {
121             length += read.length;
122         }
123         return length;
124     }
125 
126     @Override
read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer)127     public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer)
128             throws IOException {
129         int currentReadCall = mNumReadCalls;
130         ++mNumReadCalls;
131         if (mClosed.get()) {
132             throw new ClosedChannelException();
133         }
134         assertIdle();
135 
136         if (maybeFailRead(currentReadCall, uploadDataSink)) {
137             mFailed = true;
138             return;
139         }
140 
141         mReadPending = true;
142         mStarted = true;
143 
144         final boolean finalChunk = (mChunked && mNextRead == mReads.size() - 1);
145         if (mNextRead < mReads.size()) {
146             if ((byteBuffer.limit() - byteBuffer.position()) < mReads.get(mNextRead).length) {
147                 throw new IllegalStateException("Read buffer smaller than expected.");
148             }
149             byteBuffer.put(mReads.get(mNextRead));
150             ++mNextRead;
151         } else {
152             throw new IllegalStateException("Too many reads: " + mNextRead);
153         }
154 
155         Runnable completeRunnable =
156                 new Runnable() {
157                     @Override
158                     public void run() {
159                         mReadPending = false;
160                         uploadDataSink.onReadSucceeded(finalChunk);
161                     }
162                 };
163         if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
164             completeRunnable.run();
165         } else {
166             mExecutor.execute(completeRunnable);
167         }
168     }
169 
170     @Override
rewind(final UploadDataSink uploadDataSink)171     public void rewind(final UploadDataSink uploadDataSink) throws IOException {
172         ++mNumRewindCalls;
173         if (mClosed.get()) {
174             throw new ClosedChannelException();
175         }
176         assertIdle();
177 
178         if (maybeFailRewind(uploadDataSink)) {
179             mFailed = true;
180             return;
181         }
182 
183         if (mNextRead == 0) {
184             // Should never try and rewind when rewinding does nothing.
185             throw new IllegalStateException("Unexpected rewind when already at beginning");
186         }
187 
188         mRewindPending = true;
189         mNextRead = 0;
190 
191         Runnable completeRunnable =
192                 new Runnable() {
193                     @Override
194                     public void run() {
195                         mRewindPending = false;
196                         uploadDataSink.onRewindSucceeded();
197                     }
198                 };
199         if (mSuccessCallbackMode == SuccessCallbackMode.SYNC) {
200             completeRunnable.run();
201         } else {
202             mExecutor.execute(completeRunnable);
203         }
204     }
205 
assertIdle()206     private void assertIdle() {
207         if (mReadPending) {
208             throw new IllegalStateException("Unexpected operation during read");
209         }
210         if (mRewindPending) {
211             throw new IllegalStateException("Unexpected operation during rewind");
212         }
213         if (mFailed) {
214             throw new IllegalStateException("Unexpected operation after failure");
215         }
216     }
217 
maybeFailRead(int readIndex, final UploadDataSink uploadDataSink)218     private boolean maybeFailRead(int readIndex, final UploadDataSink uploadDataSink) {
219         if (readIndex != mReadFailIndex) return false;
220 
221         switch (mReadFailMode) {
222             case THROWN:
223                 throw new IllegalStateException("Thrown read failure");
224             case CALLBACK_SYNC:
225                 uploadDataSink.onReadError(new IllegalStateException("Sync read failure"));
226                 return true;
227             case CALLBACK_ASYNC:
228                 Runnable errorRunnable =
229                         new Runnable() {
230                             @Override
231                             public void run() {
232                                 uploadDataSink.onReadError(
233                                         new IllegalStateException("Async read failure"));
234                             }
235                         };
236                 mExecutor.execute(errorRunnable);
237                 return true;
238             default:
239                 return false;
240         }
241     }
242 
maybeFailRewind(final UploadDataSink uploadDataSink)243     private boolean maybeFailRewind(final UploadDataSink uploadDataSink) {
244         switch (mRewindFailMode) {
245             case THROWN:
246                 throw new IllegalStateException("Thrown rewind failure");
247             case CALLBACK_SYNC:
248                 uploadDataSink.onRewindError(new IllegalStateException("Sync rewind failure"));
249                 return true;
250             case CALLBACK_ASYNC:
251                 Runnable errorRunnable =
252                         new Runnable() {
253                             @Override
254                             public void run() {
255                                 uploadDataSink.onRewindError(
256                                         new IllegalStateException("Async rewind failure"));
257                             }
258                         };
259                 mExecutor.execute(errorRunnable);
260                 return true;
261             default:
262                 return false;
263         }
264     }
265 
266     @Override
close()267     public void close() throws IOException {
268         if (!mClosed.compareAndSet(false, true)) {
269             throw new AssertionError("Closed twice");
270         }
271         mAwaitingClose.open();
272     }
273 
assertClosed()274     public void assertClosed() {
275         mAwaitingClose.block(5000);
276         if (!mClosed.get()) {
277             throw new AssertionError("Was not closed");
278         }
279     }
280 }
281