-
Notifications
You must be signed in to change notification settings - Fork 609
/
Copy pathIPPacketOutputStream.java
141 lines (124 loc) · 4.47 KB
/
IPPacketOutputStream.java
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
/*
* Copyright (C) 2017 Genymobile
*
* 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 com.genymobile.gnirehtet;
import android.util.Log;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
/**
* Wrapper for writing one IP packet at a time to an {@link OutputStream}.
*/
@SuppressWarnings("checkstyle:MagicNumber")
public class IPPacketOutputStream extends OutputStream {
private static final String TAG = IPPacketOutputStream.class.getSimpleName();
private static final int MAX_IP_PACKET_LENGTH = 1 << 16; // packet length is stored on 16 bits
private final OutputStream target;
// must always accept 1 full packet + any partial packet
private final ByteBuffer buffer = ByteBuffer.allocate(2 * MAX_IP_PACKET_LENGTH);
public IPPacketOutputStream(OutputStream target) {
this.target = target;
}
@Override
public void close() throws IOException {
target.close();
}
@Override
public void flush() throws IOException {
target.flush();
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (len > MAX_IP_PACKET_LENGTH) {
throw new IOException("IPPacketOutputStream does not support writing more than one packet at a time");
}
// by design, the buffer must always have enough space for one packet
if (BuildConfig.DEBUG && len > buffer.remaining()) {
Log.e(TAG, len + " must be <= than " + buffer.remaining());
Log.e(TAG, buffer.toString());
throw new AssertionError("Buffer is unexpectedly full");
}
buffer.put(b, off, len);
buffer.flip();
sink();
buffer.compact();
}
@Override
public void write(int b) throws IOException {
if (!buffer.hasRemaining()) {
throw new IOException("IPPacketOutputStream buffer is full");
}
buffer.put((byte) b);
buffer.flip();
sink();
buffer.compact();
}
private void sink() throws IOException {
// sink all packets
while (sinkPacket()) {
// continue
}
}
private boolean sinkPacket() throws IOException {
int version = readPacketVersion(buffer);
if (version == -1) {
// no packet at all
return false;
}
if (version != 4) {
Log.e(TAG, "Unsupported packet received, IP version is:" + version);
Log.d(TAG, "Clearing buffer");
buffer.clear();
return false;
}
int packetLength = readPacketLength(buffer);
if (packetLength == -1 || packetLength > buffer.remaining()) {
// no packet
return false;
}
target.write(buffer.array(), buffer.arrayOffset() + buffer.position(), packetLength);
buffer.position(buffer.position() + packetLength);
return true;
}
/**
* Read the packet IP version, assuming that an IP packets is stored at absolute position 0.
*
* @param buffer the buffer
* @return the IP version, or {@code -1} if not available
*/
public static int readPacketVersion(ByteBuffer buffer) {
if (!buffer.hasRemaining()) {
// buffer is empty
return -1;
}
// version is stored in the 4 first bits
byte versionAndIHL = buffer.get(buffer.position());
return (versionAndIHL & 0xf0) >> 4;
}
/**
* Read the packet length, assuming thatan IP packet is stored at absolute position 0.
*
* @param buffer the buffer
* @return the packet length, or {@code -1} if not available
*/
public static int readPacketLength(ByteBuffer buffer) {
if (buffer.limit() < buffer.position() + 4) {
// buffer does not even contains the length field
return -1;
}
// packet length is 16 bits starting at offset 2
return Binary.unsigned(buffer.getShort(buffer.position() + 2));
}
}