aboutsummaryrefslogtreecommitdiff
path: root/samples/src/jvmMain/java/okio/samples/Interceptors.java
blob: 85cbbba55f06b15fac0cb3d1b0663265e12e21a1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
 * Copyright (C) 2018 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okio.samples;

import java.io.IOException;
import java.util.Random;
import okio.Buffer;
import okio.ForwardingSink;
import okio.ForwardingSource;
import okio.Sink;
import okio.Source;

/**
 * Demonstrates use of the {@link Buffer.UnsafeCursor} class. While other
 * samples might demonstrate real use cases, this sample hopes to show the
 * basics of using an {@link Buffer.UnsafeCursor}:
 * <ul>
 *   <li>Efficient reuse of a single cursor instance.</li>
 *   <li>Guaranteed release of an attached cursor.</li>
 *   <li>Safe traversal of the data in a Buffer.</li>
 * </ul>
 *
 * <p>This sample implements a
 * <a href="https://en.wikipedia.org/wiki/Cipher_disk">circular cipher</a> by
 * creating a Source which will intercept all bytes written to the wire and
 * decrease their value by a specific amount. Then create a Sink which will
 * intercept all bytes read from the wire and increase their value by that same
 * specific amount. This creates an incredibly insecure way of encrypting data
 * written to the wire but demonstrates the power of the
 * {@link Buffer.UnsafeCursor} class for efficient operations on the bytes
 * being written and read.
 */
public final class Interceptors {
  public void run() throws Exception {
    final byte cipher = (byte) (new Random().nextInt(256) - 128);
    System.out.println("Cipher   : " + cipher);

    Buffer wire = new Buffer();

    // Create a Sink which will intercept and negatively rotate each byte by `cipher`
    Sink sink = new InterceptingSink(wire) {
      @Override
      protected void intercept(byte[] data, int offset, int length) {
        for (int i = offset, end = offset + length; i < end; i++) {
          data[i] -= cipher;
        }
      }
    };

    // Create a Source which will intercept and positively rotate each byte by `cipher`
    Source source = new InterceptingSource(wire) {
      @Override
      protected void intercept(byte[] data, int offset, int length) {
        for (int i = offset, end = offset + length; i < end; i++) {
          data[i] += cipher;
        }
      }
    };

    Buffer transmit = new Buffer();
    transmit.writeUtf8("This is not really a secure message");
    System.out.println("Transmit : " + transmit);

    sink.write(transmit, transmit.size());
    System.out.println("Wire     : " + wire);

    Buffer receive = new Buffer();
    source.read(receive, Long.MAX_VALUE);
    System.out.println("Receive  : " + receive);
  }

  abstract class InterceptingSource extends ForwardingSource {

    private final Buffer.UnsafeCursor cursor = new Buffer.UnsafeCursor();

    InterceptingSource(Source source) {
      super(source);
    }

    @Override
    public long read(Buffer sink, long byteCount) throws IOException {
      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
      if (byteCount == 0) return 0;

      long result = super.read(sink, byteCount);
      if (result == -1L) return result;

      sink.readUnsafe(cursor);
      try {
        long remaining = result;
        for (int length = cursor.seek(sink.size() - result);
             remaining > 0 && length > 0;
             length = cursor.next()) {
          int toIntercept = (int) Math.min(length, remaining);
          intercept(cursor.data, cursor.start, toIntercept);
          remaining -= toIntercept;
        }
      } finally {
        cursor.close();
      }

      return result;
    }

    protected abstract void intercept(byte[] data, int offset, int length) throws IOException;
  }


  abstract class InterceptingSink extends ForwardingSink {

    private final Buffer.UnsafeCursor cursor = new Buffer.UnsafeCursor();

    InterceptingSink(Sink delegate) {
      super(delegate);
    }

    @Override
    public void write(Buffer source, long byteCount) throws IOException {
      if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount);
      if (source.size() < byteCount) {
        throw new IllegalArgumentException("size=" + source.size() + " byteCount=" + byteCount);
      }
      if (byteCount == 0) return;

      source.readUnsafe(cursor);
      try {
        long remaining = byteCount;
        for (int length = cursor.seek(0);
             remaining > 0 && length > 0;
             length = cursor.next()) {
          int toIntercept = (int) Math.min(length, remaining);
          intercept(cursor.data, cursor.start, toIntercept);
          remaining -= toIntercept;
        }
      } finally {
        cursor.close();
      }

      super.write(source, byteCount);
    }

    protected abstract void intercept(byte[] data, int offset, int length) throws IOException;
  }

  public static void main(String... args) throws Exception {
    new Interceptors().run();
  }
}