Coverage Summary for Class: FrameCodec (org.ethereum.net.rlpx)
Class |
Method, %
|
Line, %
|
FrameCodec |
0%
(0/8)
|
0%
(0/113)
|
FrameCodec$Frame |
0%
(0/6)
|
0%
(0/14)
|
Total |
0%
(0/14)
|
0%
(0/127)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 RSK Labs Ltd.
4 * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>)
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 package org.ethereum.net.rlpx;
21
22 import io.netty.buffer.ByteBuf;
23 import io.netty.buffer.ByteBufInputStream;
24 import io.netty.buffer.ByteBufOutputStream;
25 import org.bouncycastle.crypto.StreamCipher;
26 import org.bouncycastle.crypto.digests.KeccakDigest;
27 import org.bouncycastle.crypto.engines.AESEngine;
28 import org.bouncycastle.crypto.modes.SICBlockCipher;
29 import org.bouncycastle.crypto.params.KeyParameter;
30 import org.bouncycastle.crypto.params.ParametersWithIV;
31 import org.ethereum.util.RLP;
32
33 import java.io.*;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37
38 import static org.ethereum.util.RLP.decode2OneItem;
39
40 /**
41 * Created by devrandom on 2015-04-11.
42 */
43 public class FrameCodec {
44 private final StreamCipher enc;
45 private final StreamCipher dec;
46 private final KeccakDigest egressMac;
47 private final KeccakDigest ingressMac;
48 private final byte[] mac;
49 boolean isHeadRead;
50 private int totalBodySize;
51 private int contextId = -1;
52 private int totalFrameSize = -1;
53
54 public FrameCodec(EncryptionHandshake.Secrets secrets) {
55 this.mac = secrets.mac;
56 AESEngine encCipher = new AESEngine();
57 enc = new SICBlockCipher(encCipher);
58 enc.init(true, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[encCipher.getBlockSize()]));
59 AESEngine decCipher = new AESEngine();
60 dec = new SICBlockCipher(decCipher);
61 dec.init(false, new ParametersWithIV(new KeyParameter(secrets.aes), new byte[decCipher.getBlockSize()]));
62 egressMac = secrets.egressMac;
63 ingressMac = secrets.ingressMac;
64 }
65
66 private AESEngine makeMacCipher() {
67 // Stateless AES encryption
68 AESEngine macc = new AESEngine();
69 macc.init(true, new KeyParameter(mac));
70 return macc;
71 }
72
73 public static class Frame {
74 long type;
75 int size;
76 InputStream payload;
77
78 int totalFrameSize = -1;
79 int contextId = -1;
80
81
82 public Frame(long type, int size, InputStream payload) {
83 this.type = type;
84 this.size = size;
85 this.payload = payload;
86 }
87
88 public Frame(int type, byte[] payload) {
89 this.type = type;
90 this.size = payload.length;
91 this.payload = new ByteArrayInputStream(payload);
92 }
93
94 public int getSize() {
95 return size;
96 }
97
98 public long getType() {return type;}
99
100 public InputStream getStream() {
101 return payload;
102 }
103 public boolean isChunked() {
104 return contextId >= 0;
105 }
106
107 }
108
109 public void writeFrame(Frame frame, ByteBuf buf) throws IOException {
110 writeFrame(frame, new ByteBufOutputStream(buf));
111 }
112
113 public void writeFrame(Frame frame, OutputStream out) throws IOException {
114 byte[] headBuffer = new byte[32];
115 byte[] ptype = RLP.encodeInt((int) frame.type); // FIXME encodeLong
116 int totalSize = frame.size + ptype.length;
117 headBuffer[0] = (byte)(totalSize >> 16);
118 headBuffer[1] = (byte)(totalSize >> 8);
119 headBuffer[2] = (byte)(totalSize);
120
121 List<byte[]> headerDataElems = new ArrayList<>();
122 headerDataElems.add(RLP.encodeInt(0));
123 if (frame.contextId >= 0) {
124 headerDataElems.add(RLP.encodeInt(frame.contextId));
125 }
126 if (frame.totalFrameSize >= 0) {
127 headerDataElems.add(RLP.encodeInt(frame.totalFrameSize));
128 }
129
130 byte[] headerData = RLP.encodeList(headerDataElems.toArray(new byte[0][]));
131 System.arraycopy(headerData, 0, headBuffer, 3, headerData.length);
132
133 enc.processBytes(headBuffer, 0, 16, headBuffer, 0);
134
135 // Header MAC
136 updateMac(egressMac, headBuffer, 0, headBuffer, 16, true);
137
138 byte[] buff = new byte[256];
139 out.write(headBuffer);
140 enc.processBytes(ptype, 0, ptype.length, buff, 0);
141 out.write(buff, 0, ptype.length);
142 egressMac.update(buff, 0, ptype.length);
143 while (true) {
144 int n = frame.payload.read(buff);
145 if (n <= 0) {
146 break;
147 }
148 enc.processBytes(buff, 0, n, buff, 0);
149 egressMac.update(buff, 0, n);
150 out.write(buff, 0, n);
151 }
152 int padding = 16 - (totalSize % 16);
153 byte[] pad = new byte[16];
154 if (padding < 16) {
155 enc.processBytes(pad, 0, padding, buff, 0);
156 egressMac.update(buff, 0, padding);
157 out.write(buff, 0, padding);
158 }
159
160 // Frame MAC
161 byte[] macBuffer = new byte[egressMac.getDigestSize()];
162 doSum(egressMac, macBuffer); // fmacseed
163 updateMac(egressMac, macBuffer, 0, macBuffer, 0, true);
164 out.write(macBuffer, 0, 16);
165 }
166
167 public List<Frame> readFrames(ByteBuf buf) throws IOException {
168 return readFrames(new ByteBufInputStream(buf));
169 }
170
171 public List<Frame> readFrames(DataInput inp) throws IOException {
172 if (!isHeadRead) {
173 byte[] headBuffer = new byte[32];
174 try {
175 inp.readFully(headBuffer);
176 } catch (EOFException e) {
177 return null;
178 }
179
180 // Header MAC
181 updateMac(ingressMac, headBuffer, 0, headBuffer, 16, false);
182
183 dec.processBytes(headBuffer, 0, 16, headBuffer, 0);
184 totalBodySize = headBuffer[0];
185 totalBodySize = (totalBodySize << 8) + (headBuffer[1] & 0xFF);
186 totalBodySize = (totalBodySize << 8) + (headBuffer[2] & 0xFF);
187
188 if (totalBodySize < 0) {
189 return null;
190 }
191
192 decode2OneItem(headBuffer, 3);
193
194 contextId = -1;
195 totalFrameSize = -1;
196 isHeadRead = true;
197 }
198
199 int padding = 16 - (totalBodySize % 16);
200 if (padding == 16) {
201 padding = 0;
202 }
203 int macSize = 16;
204 byte[] buffer = new byte[totalBodySize + padding + macSize];
205 try {
206 inp.readFully(buffer);
207 } catch (EOFException e) {
208 return null;
209 }
210 int frameSize = buffer.length - macSize;
211 ingressMac.update(buffer, 0, frameSize);
212 dec.processBytes(buffer, 0, frameSize, buffer, 0);
213 int pos = 0;
214 long type = RLP.decodeInt(buffer, pos); // FIXME long
215 pos = RLP.getNextElementIndex(buffer, pos);
216 InputStream payload = new ByteArrayInputStream(buffer, pos, totalBodySize - pos);
217 int size = totalBodySize - pos;
218 byte[] macBuffer = new byte[ingressMac.getDigestSize()];
219
220 // Frame MAC
221 doSum(ingressMac, macBuffer); // fmacseed
222 updateMac(ingressMac, macBuffer, 0, buffer, frameSize, false);
223
224 isHeadRead = false;
225 Frame frame = new Frame(type, size, payload);
226 frame.contextId = contextId;
227 frame.totalFrameSize = totalFrameSize;
228 return Collections.singletonList(frame);
229 }
230
231 private byte[] updateMac(KeccakDigest mac, byte[] seed, int offset, byte[] out, int outOffset, boolean egress) throws IOException {
232 byte[] aesBlock = new byte[mac.getDigestSize()];
233 doSum(mac, aesBlock);
234 makeMacCipher().processBlock(aesBlock, 0, aesBlock, 0);
235 // Note that although the mac digest size is 32 bytes, we only use 16 bytes in the computation
236 int length = 16;
237 for (int i = 0; i < length; i++) {
238 aesBlock[i] ^= seed[i + offset];
239 }
240 mac.update(aesBlock, 0, length);
241 byte[] result = new byte[mac.getDigestSize()];
242 doSum(mac, result);
243 if (egress) {
244 System.arraycopy(result, 0, out, outOffset, length);
245 } else {
246 for (int i = 0; i < length; i++) {
247 if (out[i + outOffset] != result[i]) {
248 throw new IOException("MAC mismatch");
249 }
250 }
251 }
252 return result;
253 }
254
255 private void doSum(KeccakDigest mac, byte[] out) {
256 // doFinal without resetting the MAC by using clone of digest state
257 new KeccakDigest(mac).doFinal(out, 0);
258 }
259
260 }