package co.gongzh.procbridge; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.json.JSONException; import org.json.JSONObject; import java.io.*; /** * @author Gong Zhang */ final class Protocol { private static final byte[] FLAG = { 'p', 'b' }; private static final byte[] VERSION = { 1, 0 }; enum StatusCode { REQUEST(0), RESPONSE_GOOD(1), RESPONSE_BAD(2); int rawValue; StatusCode(int rawValue) { this.rawValue = rawValue; } @Nullable static StatusCode fromRawValue(int rawValue) { for (StatusCode sc : StatusCode.values()) { if (sc.rawValue == rawValue) { return sc; } } return null; } @NotNull Decoder makeDecoder() throws ProcBridgeException { switch (this) { case REQUEST: return new RequestDecoder(); case RESPONSE_GOOD: return new GoodResponseDecoder(); case RESPONSE_BAD: return new BadResponseDecoder(); default: throw new InternalError("unknown status code"); } } } static final String KEY_API = "api"; static final String KEY_BODY = "body"; static final String KEY_MESSAGE = "msg"; static void write(OutputStream stream, Encoder encoder) throws ProcBridgeException { try { // 1. FLAG 'p', 'b' stream.write(FLAG); // 2. VERSION stream.write(VERSION); // 3. STATUS CODE stream.write(encoder.getStatusCode().rawValue); // 4. RESERVED BYTES (2 bytes) stream.write(0); stream.write(0); // make json object byte[] data = encoder.encode(); // 5. LENGTH (4-byte, little endian) int len = data.length; int b0 = len & 0xff; int b1 = (len & 0xff00) >> 8; int b2 = (len & 0xff0000) >> 16; int b3 = (len & 0xff000000) >> 24; stream.write(b0); stream.write(b1); stream.write(b2); stream.write(b3); // 6. JSON OBJECT stream.write(data); stream.flush(); } catch (IOException e) { throw new ProcBridgeException(e); } } static Decoder read(InputStream stream) throws ProcBridgeException { try { int b; // 1. FLAG b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); if (b != FLAG[0]) throw ProcBridgeException.malformedInputData(); b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); if (b != FLAG[1]) throw ProcBridgeException.malformedInputData(); // 2. VERSION b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); if (b != VERSION[0]) throw ProcBridgeException.incompatibleVersion(); b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); if (b != VERSION[1]) throw ProcBridgeException.incompatibleVersion(); // 3. STATUS CODE b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); StatusCode statusCode = StatusCode.fromRawValue(b); if (statusCode == null) { throw ProcBridgeException.malformedInputData(); } Decoder decoder = statusCode.makeDecoder(); // 4. RESERVED BYTES (2 bytes) b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); // 5. LENGTH (little endian) int len; b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); len = b; b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); len |= (b << 8); b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); len |= (b << 16); b = stream.read(); if (b == -1) throw ProcBridgeException.unexpectedEndOfStream(); len |= (b << 24); if (len <= 0) { throw ProcBridgeException.malformedInputData(); } // 6. JSON OBJECT ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[1024]; while ((nRead = stream.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); if (buffer.size() >= len) { break; } } if (buffer.size() != len) { throw ProcBridgeException.malformedInputData(); } buffer.flush(); data = buffer.toByteArray(); String jsonText = new String(data, "UTF-8"); JSONObject obj = new JSONObject(jsonText); decoder.decode(obj); return decoder; } catch (IOException e) { throw new ProcBridgeException(e); } catch (JSONException e) { throw ProcBridgeException.malformedInputData(); } } } abstract class Encoder { abstract byte[] encode() throws ProcBridgeException; abstract Protocol.StatusCode getStatusCode(); } final class RequestEncoder extends Encoder { @NotNull private final String api; @Nullable private final JSONObject body; RequestEncoder(@NotNull String api, @Nullable JSONObject body) { if (api.isEmpty()) { throw new IllegalArgumentException("api cannot be empty"); } this.api = api; this.body = body; } @Override byte[] encode() throws ProcBridgeException { JSONObject obj = new JSONObject(); obj.put(Protocol.KEY_API, api); if (body != null) { obj.put(Protocol.KEY_BODY, body); } else { obj.put(Protocol.KEY_BODY, new JSONObject()); } String jsonText = obj.toString(); try { return jsonText.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new ProcBridgeException(e); } } @Override Protocol.StatusCode getStatusCode() { return Protocol.StatusCode.REQUEST; } } final class GoodResponseEncoder extends Encoder { @Nullable private final JSONObject body; GoodResponseEncoder(@Nullable JSONObject body) { this.body = body; } @Override byte[] encode() throws ProcBridgeException { JSONObject obj = new JSONObject(); if (body != null) { obj.put(Protocol.KEY_BODY, body); } String jsonText = obj.toString(); try { return jsonText.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new ProcBridgeException(e); } } @Override Protocol.StatusCode getStatusCode() { return Protocol.StatusCode.RESPONSE_GOOD; } } final class BadResponseEncoder extends Encoder { @Nullable private final String message; BadResponseEncoder(@Nullable String message) { this.message = message; } @Override byte[] encode() throws ProcBridgeException { JSONObject obj = new JSONObject(); if (message != null) { obj.put(Protocol.KEY_MESSAGE, message); } String jsonText = obj.toString(); try { return jsonText.getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new ProcBridgeException(e); } } @Override Protocol.StatusCode getStatusCode() { return Protocol.StatusCode.RESPONSE_BAD; } } abstract class Decoder { abstract void decode(JSONObject object) throws ProcBridgeException; abstract JSONObject getResponseBody(); abstract String getErrorMessage(); abstract RequestDecoder asRequest(); } final class RequestDecoder extends Decoder { @NotNull String api = ""; @NotNull JSONObject body = new JSONObject(); @Override void decode(JSONObject object) throws ProcBridgeException { try { String api = object.getString(Protocol.KEY_API); if (api.isEmpty()) { throw ProcBridgeException.malformedInputData(); } this.api = api; JSONObject body = object.optJSONObject(Protocol.KEY_BODY); if (body != null) { this.body = body; } } catch (JSONException ex) { throw ProcBridgeException.malformedInputData(); } } JSONObject getResponseBody() { return null; } String getErrorMessage() { return null; } @Override RequestDecoder asRequest() { return this; } } final class GoodResponseDecoder extends Decoder { @NotNull private JSONObject body = new JSONObject(); @Override void decode(JSONObject object) throws ProcBridgeException { JSONObject body = object.optJSONObject(Protocol.KEY_BODY); if (body != null) { this.body = body; } } JSONObject getResponseBody() { return body; } String getErrorMessage() { return null; } @Override RequestDecoder asRequest() { return null; } } final class BadResponseDecoder extends Decoder { @NotNull private String message = ""; @Override void decode(JSONObject object) throws ProcBridgeException { String msg = object.optString(Protocol.KEY_MESSAGE); if (msg != null) { this.message = msg; } } JSONObject getResponseBody() { return null; } String getErrorMessage() { return message; } @Override RequestDecoder asRequest() { return null; } }