/* * Copyright 2016, Stuart Douglas, and individual contributors as indicated * by the @authors tag. * * 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 org.fakereplace.server; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.lang.instrument.ClassDefinition; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.fakereplace.api.environment.ChangedClasses; import org.fakereplace.api.environment.CurrentEnvironment; import org.fakereplace.core.Agent; import org.fakereplace.logging.Logger; import org.fakereplace.replacement.AddedClass; /** * An implementation of the fakereplace client server protocol. * <p> * The basic protocol is as follows: * <p> * Client - * Magic no 0xCAFEDEAF * no classes (int) * class data (1 per class) * class name length (int) * class name * timestamp (long) * <p> * Server - * no classes (int) * class data (1 per class) * class name length * class name * <p> * Client - * no classes (int) * class data * class name length * class name * class bytes length * class bytes * * @author Stuart Douglas */ public class FakereplaceProtocol { private static final Logger log = Logger.getLogger(FakereplaceProtocol.class); public static void run(Socket socket) { DataOutputStream output = null; try { log.trace("Fakereplace update is running"); final DataInputStream input = new DataInputStream(socket.getInputStream()); output = new DataOutputStream(socket.getOutputStream()); final Map<String, Long> classes = new HashMap<String, Long>(); final Map<String, Long> resources = new HashMap<String, Long>(); int magic = input.readInt(); if (magic != 0xCAFEDEAF) { System.err.println("Fakereplace server error, wrong magic number"); return; } final String archiveName = readString(input); readAvailable(input, classes); readAvailable(input, resources); final ChangedClasses classesToReplace = CurrentEnvironment.getEnvironment().getUpdatedClasses(archiveName, classes); log.info("Fakereplace is checking for updates classes. Client sent " + classes.size() + " classes, " + classesToReplace.getChanged().size() + " need to be replaced"); final Map<String, Class> classMap = new HashMap<String, Class>(); output.writeInt(classesToReplace.getChanged().size() + classesToReplace.getNewClasses().size()); for (Class clazz : classesToReplace.getChanged()) { final String cname = clazz.getName(); output.writeInt(cname.length()); output.write(cname.getBytes()); classMap.put(cname, clazz); } for (String cname : classesToReplace.getNewClasses()) { output.writeInt(cname.length()); output.write(cname.getBytes()); } final Set<String> resourcesToReplace = CurrentEnvironment.getEnvironment().getUpdatedResources(archiveName, resources); output.writeInt(resourcesToReplace.size()); for (String cname : resourcesToReplace) { output.writeInt(cname.length()); output.write(cname.getBytes()); } output.flush(); final Set<ClassDefinition> classDefinitions = new HashSet<ClassDefinition>(); final Set<Class<?>> replacedClasses = new HashSet<Class<?>>(); final List<AddedClass> addedClassList = new ArrayList<AddedClass>(); int noClasses = input.readInt(); for (int i = 0; i < noClasses; ++i) { final String className = readString(input); int length = input.readInt(); byte[] buffer = new byte[length]; for (int j = 0; j < length; ++j) { buffer[j] = (byte) input.read(); } final Class theClass = classMap.get(className); if (theClass != null) { classDefinitions.add(new ClassDefinition(theClass, buffer)); replacedClasses.add(theClass); } else { addedClassList.add(new AddedClass(className, buffer, classesToReplace.getClassLoader())); } } final Map<String, byte[]> replacedResources = new HashMap<String, byte[]>(); int noResources = input.readInt(); for (int i = 0; i < noResources; ++i) { final String resourceName = readString(input); int length = input.readInt(); byte[] buffer = new byte[length]; for (int j = 0; j < length; ++j) { buffer[j] = (byte) input.read(); } replacedResources.put(resourceName, buffer); } Agent.redefine(classDefinitions.toArray( new ClassDefinition[classDefinitions.size()]), addedClassList.toArray(new AddedClass[addedClassList.size()])); CurrentEnvironment.getEnvironment().updateResource(archiveName, replacedResources); output.writeInt(0); } catch (Exception e) { try { output.writeInt(1); } catch (IOException e1) { //ignore } e.printStackTrace(); } finally { try { //write the result to socket.close(); } catch (IOException e) { e.printStackTrace(); } } } private static void readAvailable(final DataInputStream input, final Map<String, Long> resources) throws IOException { int noResources = input.readInt(); for (int i = 0; i < noResources; ++i) { final String resourceName = readString(input); long ts = input.readLong(); resources.put(resourceName, ts); } } private static String readString(final DataInputStream input) throws IOException { int toread = input.readInt(); byte[] buf = new byte[toread]; int read = 0; while (toread > 0 && (read = input.read(buf, read, toread)) != -1) { toread -= read; } return new String(buf); } }