/* * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.stetho.server; import android.content.Context; import android.net.LocalSocket; import javax.annotation.concurrent.NotThreadSafe; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; /** * Socket handler which is designed to detect a difference in protocol signatures very early on * in the connection to figure out which real handler to route to. This is used for performance * and backwards compatibility reasons to maintain Stetho having just one actual socket * connection despite dumpapp and DevTools now diverging in protocol. * <p /> * Note this trick is only possible if the protocol requires that the client initiate the * conversation. Otherwise, the server would be expected to say something before we know what * protocol the client is speaking. */ public class ProtocolDetectingSocketHandler extends SecureSocketHandler { private static final int SENSING_BUFFER_SIZE = 256; private final ArrayList<HandlerInfo> mHandlers = new ArrayList<>(2); public ProtocolDetectingSocketHandler(Context context) { super(context); } public void addHandler(MagicMatcher magicMatcher, SocketLikeHandler handler) { mHandlers.add(new HandlerInfo(magicMatcher, handler)); } @Override protected void onSecured(LocalSocket socket) throws IOException { LeakyBufferedInputStream leakyIn = new LeakyBufferedInputStream( socket.getInputStream(), SENSING_BUFFER_SIZE); if (mHandlers.isEmpty()) { throw new IllegalStateException("No handlers added"); } for (int i = 0, N = mHandlers.size(); i < N; i++) { HandlerInfo handlerInfo = mHandlers.get(i); leakyIn.mark(SENSING_BUFFER_SIZE); boolean matches = handlerInfo.magicMatcher.matches(leakyIn); leakyIn.reset(); if (matches) { SocketLike socketLike = new SocketLike(socket, leakyIn); handlerInfo.handler.onAccepted(socketLike); return; } } throw new IOException("No matching handler, firstByte=" + leakyIn.read()); } public interface MagicMatcher { boolean matches(InputStream in) throws IOException; } public static class ExactMagicMatcher implements MagicMatcher { private final byte[] mMagic; public ExactMagicMatcher(byte[] magic) { mMagic = magic; } @Override public boolean matches(InputStream in) throws IOException { byte[] buf = new byte[mMagic.length]; int n = in.read(buf); return n == buf.length && Arrays.equals(buf, mMagic); } } public static class AlwaysMatchMatcher implements MagicMatcher { @Override public boolean matches(InputStream in) throws IOException { return true; } } private static class HandlerInfo { public final MagicMatcher magicMatcher; public final SocketLikeHandler handler; private HandlerInfo(MagicMatcher magicMatcher, SocketLikeHandler handler) { this.magicMatcher = magicMatcher; this.handler = handler; } } }