Skip to content

Commit f04a4d6

Browse files
committed
implement List Pairings request
also keep track of admin permissions for users note that those permissions aren't actually enforced yet
1 parent 4038b55 commit f04a4d6

File tree

8 files changed

+115
-8
lines changed

8 files changed

+115
-8
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# HAP-Java 2.0.5
2+
* Implement List-Pairings method. Compatibility with new Home infrastructure from iOS 16.2?
3+
14
# HAP-Java 2.0.3
25
* Avoid unnecessary forced disconnects. Library users should be updating the configuration index anyway.
36

src/main/java/io/github/hapjava/server/HomekitAuthInfo.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import io.github.hapjava.server.impl.HomekitServer;
44
import io.github.hapjava.server.impl.crypto.HAPSetupCodeUtils;
55
import java.math.BigInteger;
6+
import java.util.Collection;
7+
import java.util.List;
68

79
/**
810
* Authentication info that must be provided when constructing a new {@link HomekitServer}. You will
@@ -65,8 +67,23 @@ default String getSetupId() {
6567
* @param username the iOS device's username. The value will not be meaningful to anything but
6668
* iOS.
6769
* @param publicKey the iOS device's public key.
70+
* @param isAdmin if the user is an admin, authorized to and/remove other users
6871
*/
69-
void createUser(String username, byte[] publicKey);
72+
default void createUser(String username, byte[] publicKey, boolean isAdmin) {
73+
createUser(username, publicKey);
74+
}
75+
76+
/**
77+
* Deprecated method to add a user, assuming all users are admins.
78+
*
79+
* <p>At least one of the createUser methods must be implemented.
80+
*
81+
* @param username the iOS device's username.
82+
* @param publicKey the iOS device's public key.
83+
*/
84+
default void createUser(String username, byte[] publicKey) {
85+
createUser(username, publicKey, true);
86+
}
7087

7188
/**
7289
* Called when an iOS device needs to remove an existing pairing. Subsequent calls to {@link
@@ -76,6 +93,15 @@ default String getSetupId() {
7693
*/
7794
void removeUser(String username);
7895

96+
/**
97+
* List all users which have been authenticated.
98+
*
99+
* @return the previously stored list of users.
100+
*/
101+
default Collection<String> listUsers() {
102+
return List.of();
103+
}
104+
79105
/**
80106
* Called when an already paired iOS device is re-connecting. The public key returned by this
81107
* method will be compared with the signature of the pair verification request to validate the
@@ -86,6 +112,16 @@ default String getSetupId() {
86112
*/
87113
byte[] getUserPublicKey(String username);
88114

115+
/**
116+
* Determine if the specified user is an admin.
117+
*
118+
* @param username the username of the iOS device to retrieve permissions for.
119+
* @return the previously stored permissions.
120+
*/
121+
default boolean userIsAdmin(String username) {
122+
return true;
123+
}
124+
89125
/**
90126
* Called to check if a user has been created. The homekit accessory advertises whether the
91127
* accessory has already been paired. At this time, it's unclear whether multiple users can be

src/main/java/io/github/hapjava/server/impl/connections/HttpSession.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public HttpResponse handleRequest(HttpRequest request) throws IOException {
6767

6868
public HttpResponse handleAuthenticatedRequest(HttpRequest request) throws IOException {
6969
advertiser.setDiscoverable(
70-
false); // brigde is already bound and should not be discoverable anymore
70+
false); // bridge is already bound and should not be discoverable anymore
7171
try {
7272
switch (request.getUri()) {
7373
case "/accessories":

src/main/java/io/github/hapjava/server/impl/pairing/FinalPairHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ private HttpResponse createUser(byte[] username, byte[] ltpk, byte[] proof) thro
6565
if (!new EdsaVerifier(ltpk).verify(completeData, proof)) {
6666
throw new Exception("Invalid signature");
6767
}
68-
authInfo.createUser(authInfo.getMac() + new String(username, StandardCharsets.UTF_8), ltpk);
68+
authInfo.createUser(
69+
authInfo.getMac() + new String(username, StandardCharsets.UTF_8), ltpk, true);
6970
return createResponse();
7071
}
7172

src/main/java/io/github/hapjava/server/impl/pairing/MessageType.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ public enum MessageType {
99
ENCRYPTED_DATA(5),
1010
STATE(6),
1111
ERROR(7),
12-
SIGNATURE(10);
12+
SIGNATURE(0x0a),
13+
PERMISSIONS(0x0b),
14+
FRAGMENT_DATA(0x0c),
15+
FRAGMENT_LAST(0x0d),
16+
FLAGS(0x13),
17+
SEPARATOR(0xff);
1318

1419
private final short key;
1520

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package io.github.hapjava.server.impl.pairing;
2+
3+
public enum PairingMethod {
4+
PAIR_SETUP(0),
5+
PAIR_SETUP_WITH_AUTH(1),
6+
PAIR_VERIFY(2),
7+
ADD_PAIRING(3),
8+
REMOVE_PAIRING(4),
9+
LIST_PAIRINGS(5);
10+
11+
private final byte value;
12+
13+
PairingMethod(byte value) {
14+
this.value = value;
15+
}
16+
17+
PairingMethod(int value) {
18+
this.value = (byte) value;
19+
}
20+
21+
public byte getValue() {
22+
return value;
23+
}
24+
}

src/main/java/io/github/hapjava/server/impl/pairing/PairingUpdateController.java

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import io.github.hapjava.server.impl.pairing.TypeLengthValueUtils.DecodeResult;
88
import java.io.IOException;
99
import java.nio.charset.StandardCharsets;
10+
import java.util.Collection;
11+
import java.util.Iterator;
1012

1113
public class PairingUpdateController {
1214

@@ -22,19 +24,45 @@ public HttpResponse handle(HttpRequest request) throws IOException {
2224
DecodeResult d = TypeLengthValueUtils.decode(request.getBody());
2325

2426
int method = d.getByte(MessageType.METHOD);
25-
if (method == 3) { // Add pairing
27+
if (method == PairingMethod.ADD_PAIRING.getValue()) {
2628
byte[] username = d.getBytes(MessageType.USERNAME);
2729
byte[] ltpk = d.getBytes(MessageType.PUBLIC_KEY);
28-
authInfo.createUser(authInfo.getMac() + new String(username, StandardCharsets.UTF_8), ltpk);
29-
} else if (method == 4) { // Remove pairing
30+
byte permissions = d.getByte(MessageType.PERMISSIONS);
31+
authInfo.createUser(
32+
authInfo.getMac() + new String(username, StandardCharsets.UTF_8), ltpk, permissions == 1);
33+
} else if (method == PairingMethod.REMOVE_PAIRING.getValue()) {
3034
byte[] username = d.getBytes(MessageType.USERNAME);
3135
authInfo.removeUser(authInfo.getMac() + new String(username, StandardCharsets.UTF_8));
3236
if (!authInfo.hasUser()) {
3337
advertiser.setDiscoverable(true);
3438
}
39+
} else if (method == PairingMethod.LIST_PAIRINGS.getValue()) {
40+
TypeLengthValueUtils.Encoder e = TypeLengthValueUtils.getEncoder();
41+
42+
Collection<String> usernames = authInfo.listUsers();
43+
boolean first = true;
44+
Iterator<String> iterator = usernames.iterator();
45+
while (iterator.hasNext()) {
46+
String username = iterator.next();
47+
if (first) {
48+
e.add(MessageType.STATE, (byte) 2);
49+
first = false;
50+
} else {
51+
e.add(MessageType.SEPARATOR);
52+
}
53+
e.add(MessageType.USERNAME, username);
54+
e.add(MessageType.PUBLIC_KEY, authInfo.getUserPublicKey(username));
55+
e.add(MessageType.PERMISSIONS, (short) (authInfo.userIsAdmin(username) ? 1 : 0));
56+
}
57+
;
58+
59+
return new PairingResponse(e.toByteArray());
3560
} else {
3661
throw new RuntimeException("Unrecognized method: " + method);
3762
}
38-
return new PairingResponse(new byte[] {0x06, 0x01, 0x02});
63+
64+
TypeLengthValueUtils.Encoder e = TypeLengthValueUtils.getEncoder();
65+
e.add(MessageType.STATE, (byte) 2);
66+
return new PairingResponse(e.toByteArray());
3967
}
4068
}

src/main/java/io/github/hapjava/server/impl/pairing/TypeLengthValueUtils.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.io.IOException;
66
import java.io.InputStream;
77
import java.math.BigInteger;
8+
import java.nio.charset.StandardCharsets;
89
import java.util.HashMap;
910
import java.util.Map;
1011

@@ -37,6 +38,11 @@ private Encoder() {
3738
baos = new ByteArrayOutputStream();
3839
}
3940

41+
public void add(MessageType type) {
42+
baos.write(type.getKey());
43+
baos.write(0);
44+
}
45+
4046
public void add(MessageType type, BigInteger i) throws IOException {
4147
add(type, ByteUtils.toByteArray(i));
4248
}
@@ -58,6 +64,10 @@ public void add(MessageType type, byte[] bytes) throws IOException {
5864
}
5965
}
6066

67+
public void add(MessageType type, String string) throws IOException {
68+
add(type, string.getBytes(StandardCharsets.UTF_8));
69+
}
70+
6171
public byte[] toByteArray() {
6272
return baos.toByteArray();
6373
}

0 commit comments

Comments
 (0)