/** * Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain a * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package com.sixt.service.framework.protobuf; import com.google.common.primitives.Ints; import com.sixt.service.framework.json.JsonRpcResponse; import com.sixt.service.framework.rpc.RpcCallException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Arrays; /** * Micro framework implementation that encapsulates the protobuf * envelope and body for a response (for use in parsing!) */ public class ProtobufRpcResponse { public final static int MAX_HEADER_SIZE = 1048576; private static final Logger logger = LoggerFactory.getLogger(ProtobufRpcResponse.class); private String serviceMethod; private long sequenceNumber; private String errorMessage; private byte payloadData[]; public ProtobufRpcResponse(byte[] data) throws RpcCallException { //we may get a json error even though we make a protobuf call if (data[0] == '{' && data[data.length - 1] == '}') { readRpcError(new String(data)); return; } int headerLength = Ints.fromByteArray(data); if (headerLength < 0 || headerLength > MAX_HEADER_SIZE) { StringBuilder sb = new StringBuilder(); sb.append("Unexpected header length: ").append(headerLength). append(", data: ").append(ensurePrintable(data, 256)); String message = sb.toString(); logger.warn(message); throw new RpcCallException(RpcCallException.Category.InternalServerError, message); } logger.debug("headerLength = {}", headerLength); int offset = 4; byte headerData[] = Arrays.copyOfRange(data, offset, offset + headerLength); offset += headerLength; byte payloadLengthBuffer[] = Arrays.copyOfRange(data, offset, offset + 4); offset += 4; int payloadLength = Ints.fromByteArray(payloadLengthBuffer); payloadData = Arrays.copyOfRange(data, offset, offset + payloadLength); RpcEnvelope.Response responseHeader = ProtobufUtil. byteArrayToProtobuf(headerData, RpcEnvelope.Response.class); serviceMethod = responseHeader.getServiceMethod(); sequenceNumber = responseHeader.getSequenceNumber(); errorMessage = responseHeader.getError(); } /** * Intended only for testing */ public ProtobufRpcResponse(String serviceMethod, long sequenceNumber, String errorMessage, byte[] payloadData) { this.serviceMethod = serviceMethod; this.sequenceNumber = sequenceNumber; this.errorMessage = errorMessage; this.payloadData = payloadData; } //array printer which shows non-ascii characters as their hex value protected static String ensurePrintable(byte[] data, int maxLength) { StringBuilder sb = new StringBuilder(); sb.append('['); int length = data.length; for (int i = 0; i < length && i < maxLength; i++) { if (i > 0) { sb.append(", "); } byte b = data[i]; if (b > 32) { sb.append((char) data[i]); } else { sb.append("0x").append(Integer.toHexString(b & 0x000000ff)); } } if (length > maxLength) { sb.append(", ..."); } sb.append(']'); return sb.toString(); } private void readRpcError(String data) { try { JsonRpcResponse json = JsonRpcResponse.fromString(data); this.errorMessage = json.getError().toString(); } catch (Exception ex) { logger.warn("Caught exception parsing response"); } } public String getErrorMessage() { return errorMessage; } public byte[] getPayloadData() { return payloadData; } }