]> git.basschouten.com Git - openhab-addons.git/blob
b2e533e5719b7f29b189c7d5089abc17196d2e68
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.playstation.internal;
14
15 import java.nio.ByteBuffer;
16 import java.nio.ByteOrder;
17 import java.nio.charset.StandardCharsets;
18
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20
21 /**
22  * The {@link PS4PacketHandler} is responsible for creating and parsing
23  * packets to / from the PS4.
24  *
25  * @author Fredrik Ahlström - Initial contribution
26  */
27 @NonNullByDefault
28 public class PS4PacketHandler {
29
30     private static final String APPLICATION_NAME = "openHAB PlayStation 4 Binding";
31     private static final String DEVICE_NAME = "openHAB Server";
32
33     private static final String OS_VERSION = "8.1.0";
34     private static final String DDP_VERSION = "device-discovery-protocol-version:00020020\n";
35     static final int REQ_VERSION = 0x20000;
36
37     private PS4PacketHandler() {
38         // Don't instantiate
39     }
40
41     /**
42      * Allocates a new ByteBuffer of exactly size.
43      *
44      * @param size The size of the packet.
45      * @param cmd The command to add to the packet.
46      * @return A ByteBuffer of exactly size number of bytes.
47      */
48     static ByteBuffer newPacketOfSize(int size, PS4Command cmd) {
49         ByteBuffer packet = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
50         packet.putInt(size).putInt(cmd.value);
51         return packet;
52     }
53
54     /**
55      * Allocates a new ByteBuffer of size aligned to be a multiple of 16 bytes.
56      *
57      * @param size The size of the data in the packet.
58      * @param cmd The command to add to the packet.
59      * @return A ByteBuffer aligned to 16 byte size.
60      */
61     private static ByteBuffer newPacketForEncryption(int size, PS4Command cmd) {
62         int realSize = (((size + 15) >> 4) << 4);
63         ByteBuffer packet = ByteBuffer.allocate(realSize).order(ByteOrder.LITTLE_ENDIAN);
64         packet.putInt(size).putInt(cmd.value);
65         return packet;
66     }
67
68     static byte[] makeSearchPacket() {
69         StringBuilder packet = new StringBuilder("SRCH * HTTP/1.1\n");
70         packet.append(DDP_VERSION);
71         return packet.toString().getBytes(StandardCharsets.UTF_8);
72     }
73
74     /**
75      * A packet to start up the PS4 from standby mode.
76      *
77      * @param userCredential A 64 character long hex string.
78      * @return A wake-up packet.
79      */
80     static byte[] makeWakeupPacket(String userCredential) {
81         StringBuilder packet = new StringBuilder("WAKEUP * HTTP/1.1\n");
82         packet.append("client-type:a\n"); // i or a
83         packet.append("auth-type:C\n");
84         packet.append("model:a\n");
85         packet.append("app-type:g\n"); // c or g
86         packet.append("user-credential:" + userCredential + "\n");
87         packet.append(DDP_VERSION);
88         return packet.toString().getBytes(StandardCharsets.UTF_8);
89     }
90
91     /**
92      * A packet to start up communication with the PS4.
93      *
94      * @param userCredential A 64 character long hex string
95      * @return A launch packet.
96      */
97     static byte[] makeLaunchPacket(String userCredential) {
98         StringBuilder packet = new StringBuilder("LAUNCH * HTTP/1.1\n");
99         packet.append("user-credential:" + userCredential + "\n");
100         packet.append(DDP_VERSION);
101         return packet.toString().getBytes(StandardCharsets.UTF_8);
102     }
103
104     static ByteBuffer makeHelloPacket() {
105         ByteBuffer packet = newPacketOfSize(28, PS4Command.HELLO_REQ);
106         packet.putInt(REQ_VERSION);
107         packet.put(new byte[16]); // Seed = 16 bytes
108         packet.rewind();
109         return packet;
110     }
111
112     /**
113      * Make a login packet, also used when pairing the device to the PS4.
114      *
115      * @param userCredential
116      * @param passCode
117      * @param pairingCode
118      * @return
119      */
120     static ByteBuffer makeLoginPacket(String userCredential, String passCode, String pairingCode) {
121         ByteBuffer packet = newPacketForEncryption(16 + 64 + 256 + 16 + 16 + 16, PS4Command.LOGIN_REQ);
122         if (passCode.length() == 4) {
123             packet.put(passCode.getBytes(), 0, 4); // Pass-code
124         }
125         packet.position(12);
126         packet.putInt(0x0F00); // Magic number (was 0x0201 before).
127         if (userCredential.length() == 64) {
128             packet.put(userCredential.getBytes(StandardCharsets.US_ASCII), 0, 64);
129         }
130         packet.position(16 + 64);
131         packet.put(APPLICATION_NAME.getBytes(StandardCharsets.UTF_8)); // app_label
132         packet.position(16 + 64 + 256);
133         packet.put(OS_VERSION.getBytes()); // os_version
134         packet.position(16 + 64 + 256 + 16);
135         packet.put(DEVICE_NAME.getBytes(StandardCharsets.UTF_8)); // Model, name of paired unit, shown on the PS4
136                                                                   // in the settings view.
137         packet.position(16 + 64 + 256 + 16 + 16);
138         if (pairingCode.length() == 8) {
139             packet.put(pairingCode.getBytes(), 0, 8); // Pairing-code
140         }
141         return packet;
142     }
143
144     /**
145      * Required for getting HPPTd status. Tell the PS4 who we are?
146      *
147      * @param clientID Example: "com.playstation.mobile2ndscreen".
148      * @param clientVersion Example: "18.9.3"
149      * @return A ClientID packet.
150      */
151     static ByteBuffer makeClientIDPacket(String clientID, String clientVersion) {
152         ByteBuffer packet = newPacketForEncryption(8 + 128 + 32, PS4Command.CLIENT_IDENTITY_REQ);
153         int length = clientID.length();
154         if (length < 128) {
155             packet.put(clientID.getBytes(StandardCharsets.UTF_8));
156         }
157         packet.position(8 + 128);
158         length = clientVersion.length();
159         if (length < 32) {
160             packet.put(clientVersion.getBytes(StandardCharsets.UTF_8));
161         }
162         return packet;
163     }
164
165     /**
166      * Ask for PS4 status.
167      *
168      * @param status Can be one of 0 or 1?
169      * @return A ServerStatus packet.
170      */
171     static ByteBuffer makeStatusPacket(int status) {
172         ByteBuffer packet = newPacketForEncryption(12, PS4Command.STATUS_REQ);
173         packet.putInt(status); // status
174         return packet;
175     }
176
177     /**
178      * Makes a packet that puts the PS4 in standby mode.
179      *
180      * @return A standby-packet.
181      */
182     static ByteBuffer makeStandbyPacket() {
183         return newPacketForEncryption(8, PS4Command.STANDBY_REQ);
184     }
185
186     /**
187      * Tries to start an application on the PS4.
188      *
189      * @param applicationId The ID of the application.
190      * @return An appStart-packet
191      */
192     static ByteBuffer makeApplicationPacket(String applicationId) {
193         ByteBuffer packet = newPacketForEncryption(8 + 16, PS4Command.APP_START_REQ);
194         packet.put(applicationId.getBytes(StandardCharsets.UTF_8)); // Application Id (CUSAxxxxx)
195         return packet;
196     }
197
198     /**
199      * Makes a packet that closes down the connection with the PS4.
200      *
201      * @return A ByeBye-packet.
202      */
203     static ByteBuffer makeByebyePacket() {
204         return newPacketForEncryption(8, PS4Command.BYEBYE_REQ);
205     }
206
207     /**
208      * This doesn't seem to do anything?
209      *
210      * @return A logout-packet.
211      */
212     static ByteBuffer makeLogoutPacket() {
213         return newPacketForEncryption(8, PS4Command.LOGOUT_REQ);
214     }
215
216     /**
217      *
218      * @return A screenshot-packet?
219      */
220     static ByteBuffer makeScreenShotPacket() {
221         ByteBuffer packet = newPacketForEncryption(12, PS4Command.SCREEN_SHOT_REQ);
222         packet.putInt(1);
223         return packet;
224     }
225
226     static String parseHTTPdPacket(ByteBuffer buffer) {
227         buffer.position(0);
228         int status = buffer.getInt(8);
229         int port = buffer.getInt(12);
230         int option = buffer.getInt(16);
231         return String.format("status:%d, port:%d, option:%08x.", status, port, option);
232     }
233
234     /**
235      * Tell the PS4 that we want to get info about the OnScreenKeyboard.
236      *
237      * @return An OSKStartPacket.
238      */
239     static ByteBuffer makeOSKStartPacket() {
240         return newPacketForEncryption(8, PS4Command.OSK_START_REQ);
241     }
242
243     /**
244      * Send text to the OSK on the PS4. Replaces all the text as it is now.
245      *
246      * @param text
247      * @return An OSKStringChangePacket.
248      */
249     static ByteBuffer makeOSKStringChangePacket(String text) {
250         byte[] chars = text.getBytes(StandardCharsets.UTF_16LE);
251         ByteBuffer packet = newPacketForEncryption(28 + chars.length, PS4Command.OSK_CHANGE_STRING_REQ);
252         packet.putInt(text.length()); // preEditIndex
253         packet.putInt(0); // preEditLength
254         packet.putInt(text.length()); // caretIndex
255         packet.putInt(0); // editIndex
256         packet.putInt(0); // editLength
257         packet.put(chars);
258         return packet;
259     }
260
261     /**
262      * Parses out the text from an OSKStringChange-packet.
263      *
264      * @param buffer The received packet from the PS4.
265      * @return The text in the packet.
266      */
267     static String parseOSKStringChangePacket(ByteBuffer buffer) {
268         buffer.position(0);
269         int length = buffer.getInt() - 28;
270         byte[] chars = new byte[length];
271         buffer.position(28);
272         buffer.get(chars);
273         return new String(chars, StandardCharsets.UTF_16LE);
274     }
275
276     /**
277      *
278      * @param command 0 = return, 1 = close.
279      * @return
280      */
281     static ByteBuffer makeOSKControlPacket(int command) {
282         ByteBuffer packet = newPacketForEncryption(12, PS4Command.OSK_CONTROL_REQ);
283         packet.putInt(command);
284         return packet;
285     }
286
287     static ByteBuffer makeRemoteControlPacket(int pushedKey) {
288         ByteBuffer packet = newPacketForEncryption(16, PS4Command.REMOTE_CONTROL_REQ);
289         packet.putInt(pushedKey);
290         packet.putInt(0); // HoldTime in milliseconds
291         return packet;
292     }
293
294     /**
295      *
296      * @param i only 0?
297      * @return
298      */
299     static ByteBuffer makeCommentViewerStart(int i) {
300         ByteBuffer packet = newPacketForEncryption(12, PS4Command.COMMENT_VIEWER_START_REQ);
301         packet.putInt(i);
302         return packet;
303     }
304
305     /**
306      *
307      * @param type Can be 5?
308      * @param info If type is 5 only check bit 0.
309      * @return
310      */
311     static ByteBuffer makeCommentViewerEvent(int type, int info) {
312         ByteBuffer packet = newPacketForEncryption(16, PS4Command.COMMENT_VIEWER_EVENT);
313         packet.putInt(type);
314         packet.putInt(info);
315         return packet;
316     }
317
318     static ByteBuffer makeCommentViewerSendPacket(int i, String text) {
319         byte[] chars = (text.length() > 60 ? text.substring(0, 60) : text).getBytes(StandardCharsets.UTF_8);
320         ByteBuffer packet = newPacketForEncryption(12 + chars.length, PS4Command.OSK_CHANGE_STRING_REQ);
321         packet.putInt(i);
322         packet.put(chars);
323         return packet;
324     }
325 }