forked from hap-java/HAP-Java
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConnectionImpl.java
More file actions
155 lines (141 loc) · 5.31 KB
/
ConnectionImpl.java
File metadata and controls
155 lines (141 loc) · 5.31 KB
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
package io.github.hapjava.server.impl.connections;
import io.github.hapjava.server.HomekitAuthInfo;
import io.github.hapjava.server.impl.HomekitRegistry;
import io.github.hapjava.server.impl.crypto.ChachaDecoder;
import io.github.hapjava.server.impl.crypto.ChachaEncoder;
import io.github.hapjava.server.impl.http.HomekitClientConnection;
import io.github.hapjava.server.impl.http.HttpRequest;
import io.github.hapjava.server.impl.http.HttpResponse;
import io.github.hapjava.server.impl.jmdns.JmdnsHomekitAdvertiser;
import io.github.hapjava.server.impl.pairing.UpgradeResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Collection;
import java.util.function.Consumer;
import org.bouncycastle.util.Pack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class ConnectionImpl implements HomekitClientConnection {
private final HttpSession httpSession;
private LengthPrefixedByteArrayProcessor binaryProcessor;
private int inboundBinaryMessageCount = 0;
private int outboundBinaryMessageCount = 0;
private byte[] readKey;
private byte[] writeKey;
private String username;
private boolean isUpgraded = false;
private final Consumer<HttpResponse> outOfBandMessageCallback;
private final SubscriptionManager subscriptions;
private static final Logger LOGGER = LoggerFactory.getLogger(HomekitClientConnection.class);
public ConnectionImpl(
HomekitAuthInfo authInfo,
HomekitRegistry registry,
Consumer<HttpResponse> outOfBandMessageCallback,
SubscriptionManager subscriptions,
JmdnsHomekitAdvertiser advertiser) {
httpSession = new HttpSession(authInfo, registry, subscriptions, this, advertiser);
this.outOfBandMessageCallback = outOfBandMessageCallback;
this.subscriptions = subscriptions;
}
@Override
public synchronized HttpResponse handleRequest(HttpRequest request) throws IOException {
return doHandleRequest(request);
}
private HttpResponse doHandleRequest(HttpRequest request) throws IOException {
HttpResponse response =
isUpgraded
? httpSession.handleAuthenticatedRequest(request)
: httpSession.handleRequest(request);
if (response instanceof UpgradeResponse) {
isUpgraded = true;
readKey = ((UpgradeResponse) response).getReadKey().array();
writeKey = ((UpgradeResponse) response).getWriteKey().array();
username = ((UpgradeResponse) response).getUsername();
}
LOGGER.trace("{} {} {}", response.getStatusCode(), request.getMethod(), request.getUri());
return response;
}
@Override
public byte[] decryptRequest(byte[] ciphertext) {
if (!isUpgraded) {
throw new RuntimeException("Cannot handle binary before connection is upgraded");
}
if (binaryProcessor == null) {
binaryProcessor = new LengthPrefixedByteArrayProcessor();
}
Collection<byte[]> res = binaryProcessor.handle(ciphertext);
if (res.isEmpty()) {
return new byte[0];
} else {
try (ByteArrayOutputStream decrypted = new ByteArrayOutputStream()) {
res.stream()
.map(msg -> decrypt(msg))
.forEach(
bytes -> {
try {
decrypted.write(bytes);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
return decrypted.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
@Override
public byte[] encryptResponse(byte[] response) throws IOException {
int offset = 0;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
while (offset < response.length) {
short length = (short) Math.min(response.length - offset, 0x400);
byte[] lengthBytes =
ByteBuffer.allocate(2).order(ByteOrder.LITTLE_ENDIAN).putShort(length).array();
baos.write(lengthBytes);
byte[] nonce = Pack.longToLittleEndian(outboundBinaryMessageCount++);
byte[] plaintext;
if (response.length == length) {
plaintext = response;
} else {
plaintext = new byte[length];
System.arraycopy(response, offset, plaintext, 0, length);
}
offset += length;
baos.write(new ChachaEncoder(writeKey, nonce).encodeCiphertext(plaintext, lengthBytes));
}
return baos.toByteArray();
}
}
private byte[] decrypt(byte[] msg) {
byte[] mac = new byte[16];
byte[] ciphertext = new byte[msg.length - 16];
System.arraycopy(msg, 0, ciphertext, 0, msg.length - 16);
System.arraycopy(msg, msg.length - 16, mac, 0, 16);
byte[] additionalData =
ByteBuffer.allocate(2)
.order(ByteOrder.LITTLE_ENDIAN)
.putShort((short) (msg.length - 16))
.array();
try {
byte[] nonce = Pack.longToLittleEndian(inboundBinaryMessageCount++);
return new ChachaDecoder(readKey, nonce).decodeCiphertext(mac, additionalData, ciphertext);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void close() {
subscriptions.removeConnection(this);
}
@Override
public void outOfBand(HttpResponse message) {
outOfBandMessageCallback.accept(message);
}
@Override
public String getUsername() {
return username;
}
}