diff options
6 files changed, 343 insertions, 58 deletions
diff --git a/docs/TCF Service - Registers.html b/docs/TCF Service - Registers.html index 1638c2306..22d84ffe2 100644 --- a/docs/TCF Service - Registers.html +++ b/docs/TCF Service - Registers.html @@ -15,6 +15,8 @@ <li><a href='#CmdGetChildren'>Get Children</a> <li><a href='#CmdSetRegister'>Set Register</a> <li><a href='#CmdGetRegister'>Get Register</a> + <li><a href='#CmdSetMultiple'>Set Multiple Registers</a> + <li><a href='#CmdGetMultiple'>Get Multiple Registers</a> </ul> <li><a href='#Events'>Events</a> <li><a href='#API'>API</a> @@ -27,6 +29,14 @@ and event parameters are encoded as zero terminated <a href='TCF Specification.h <p>The service uses standard format for error reports, see <a href='TCF Services.html#ErrorFormat'>Error Report Format</a>.</p> + +In addition to commands that can set/get individual register context values, the service defines commands to set/get values at +multiple locations. This allows: +<ol> + <li> to get/set multiple register contexts in one command + <li> to specify offset and size for get/set on large register groups + <li> to get/set trunkated register values, e.g. only the low 32 bits of a 64-bit register +</ol> <h2><a name='Cmds'>Commands</a></h2> @@ -187,6 +197,64 @@ R • <i><token></i> • <i><error report></i> • <i><string: value> <p>Error report provides integer error code and a short, human readable explanation of error. Value is BASE64 encoded byte array of binary data. Array size should match the size of the register.</p> +<h3><a name='CmdSetMultiple'>Set Multiple Registers</a></h3> + +<pre><b><font face="Courier New" size=2 color=#333399> +C • <token> • Registers • setm • <i><array of locations></i> • <i><string: value></i> • + +<i><array of locations></i> + <font face=Wingdings>Ø</font> [ <i><location list></i> ] + +<i><location list></i> + <font face=Wingdings>Ø</font> <i><location></i> + <font face=Wingdings>Ø</font> <i><location list></i> , <i><location></i> + +<i><location></i> + <font face=Wingdings>Ø</font> [ <i><string: register context ID></i> , <i><int: offset in bytes></i> , <i><int: size in bytes></i> ] +</font></b></pre> + +<p>Writes value into given list of locations in registers. Each location is represented by 3-element array that consists of +context ID, offset in the context in bytes and value size in bytes. +Value is BASE64 encoded byte array of binary data. Byte array size should match the sum of location sizes.</p> + +<p>Result message:</p> + +<pre><b><font face="Courier New" size=2 color=#333399> +R • <i><token></i> • <i><error report></i> • +</font></b></pre> + +<p>Error report provides integer error code and a short, human readable explanation +of error.</p> + +<h3><a name='CmdGetMultiple'>Get Multiple Registers</a></h3> + +<pre><b><font face="Courier New" size=2 color=#333399> +C • <token> • Registers • getm • <i><string: context ID></i> • + +<i><array of locations></i> + <font face=Wingdings>Ø</font> [ <i><location list></i> ] + +<i><location list></i> + <font face=Wingdings>Ø</font> <i><location></i> + <font face=Wingdings>Ø</font> <i><location list></i> , <i><location></i> + +<i><location></i> + <font face=Wingdings>Ø</font> [ <i><string: register context ID></i> , <i><int: offset in bytes></i> , <i><int: size in bytes></i> ] +</font></b></pre> + +<p>Reads register values from given list of locations in registers. Each location is represented by 3-element array that consists of +context ID, offset in the context in bytes and value size in bytes. +</p> + +<p>Result message:</p> + +<pre><b><font face="Courier New" size=2 color=#333399> +R • <i><token></i> • <i><error report></i> • <i><string: value></i> • +</font></b></pre> + +<p>Error report provides integer error code and a short, human readable explanation +of error. Value is BASE64 encoded byte array of binary data. Byte array size should match the sum of location sizes.</p> + <h2><a name='Events'>Events</a></h2> <p>Registers service broadcasts notification events when registers contexts are changed, and when @@ -231,16 +299,6 @@ E • Registers • registerChanged • <i><string: context ID></i> • PROP_VALUES = "Values"; <font color=#3F5FBF>/** - * Standard known formats for register data. - */</font> - <font color=#7F0055>static final</font> String - FORMAT_BINARY = "Binary", - FORMAT_OCTAL = "Octal", - FORMAT_DECIMAL = "Decimal", - FORMAT_HEX = "Hex", - FORMAT_NATURAL = "Natural"; - - <font color=#3F5FBF>/** * Retrieve context info for given context ID. * * <font color=#7F9FBF>@param</font> id – context ID. @@ -452,6 +510,43 @@ E • Registers • registerChanged • <i><string: context ID></i> • } <font color=#3F5FBF>/** + * Read values of multiple locations in registers. + * <font color=#7F9FBF>@param</font> locs - array of data locations. + * <font color=#7F9FBF>@param</font> done - call back object. + * <font color=#7F9FBF>@return</font> - pending command handle. + */</font> + IToken getm(Location[] locs, DoneGet done); + + <font color=#3F5FBF>/** + * Set values of multiple locations in registers. + * <font color=#7F9FBF>@param</font> locs - array of data locations. + * <font color=#7F9FBF>@param</font> value - value to write into the context. + * <font color=#7F9FBF>@param</font> done - call back object. + * <font color=#7F9FBF>@return</font> - pending command handle. + */</font> + IToken setm(Location[] locs, byte[] value, DoneSet done); + + <font color=#3F5FBF>/** + * Class Location represents value location in register context + */</font> + <font color=#7F0055>final class</font> Location { + <font color=#3F5FBF>/** Register context ID */</font> + <font color=#7F0055>public final</font> String id; + + <font color=#3F5FBF>/** offset in the context, in bytes */</font> + <font color=#7F0055>public final int</font> offs; + + <font color=#3F5FBF>/** value size in byte */</font> + <font color=#7F0055>public final int</font> size; + + <font color=#7F0055>public</font> Location(String id, <font color=#7F0055>int</font> offs, <font color=#7F0055>int</font> size) { + <font color=#7F0055>this</font>.id = id; + <font color=#7F0055>this</font>.offs = offs; + <font color=#7F0055>this</font>.size = size; + } + } + + <font color=#3F5FBF>/** * 'get' command call back interface. */</font> <font color=#7F0055>interface</font> DoneGet { diff --git a/plugins/org.eclipse.tm.tcf.debug.ui/src/org/eclipse/tm/internal/tcf/debug/ui/launch/TCFSelfTest.java b/plugins/org.eclipse.tm.tcf.debug.ui/src/org/eclipse/tm/internal/tcf/debug/ui/launch/TCFSelfTest.java index 6056358e7..d3942284b 100644 --- a/plugins/org.eclipse.tm.tcf.debug.ui/src/org/eclipse/tm/internal/tcf/debug/ui/launch/TCFSelfTest.java +++ b/plugins/org.eclipse.tm.tcf.debug.ui/src/org/eclipse/tm/internal/tcf/debug/ui/launch/TCFSelfTest.java @@ -20,6 +20,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; @@ -951,6 +952,46 @@ class TCFSelfTest { } })); } + if (!reg_map.isEmpty()) { + Random rnd = new Random(); + List<IRegisters.Location> locs = new ArrayList<IRegisters.Location>(); + String[] ids = reg_map.keySet().toArray(new String[reg_map.size()]); + for (int i = 0; i < rnd.nextInt(32); i++) { + String id = ids[rnd.nextInt(ids.length)]; + IRegisters.RegistersContext ctx = reg_map.get(id); + if (!ctx.isReadable()) continue; + if (ctx.isReadOnce()) continue; + int offs = rnd.nextInt(ctx.getSize()); + int size = rnd.nextInt(ctx.getSize() - offs) + 1; + locs.add(new IRegisters.Location(id, offs, size)); + } + final IRegisters.Location[] loc_arr = locs.toArray(new IRegisters.Location[locs.size()]); + cmds.add(rg.getm(loc_arr, new IRegisters.DoneGet() { + public void doneGet(IToken token, Exception error, byte[] value) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error != null) { + for (IToken t : cmds) t.cancel(); + exit(error); + return; + } + cmds.add(rg.setm(loc_arr, value, new IRegisters.DoneSet() { + public void doneSet(IToken token, Exception error) { + cmds.remove(token); + if (suspended.get(sc.id) != sc) return; + if (error != null) { + for (IToken t : cmds) t.cancel(); + exit(error); + return; + } + if (cmds.isEmpty()) { + resume(sc); + } + } + })); + } + })); + } if (cmds.isEmpty()) { resume(sc); } diff --git a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/local/LocatorService.java b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/local/LocatorService.java index ace101013..dda4db59e 100644 --- a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/local/LocatorService.java +++ b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/local/LocatorService.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.eclipse.tm.internal.tcf.services.local; +import java.lang.reflect.Method; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; @@ -18,6 +19,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.tm.internal.tcf.core.LocalPeer; @@ -174,52 +176,61 @@ public class LocatorService implements ILocator { * socket.send(new DatagramPacket(buf, buf.length, ia.getBroadcast(), 1534)); * } */ - Enumeration<InetAddress> n = f.getInetAddresses(); - while (n.hasMoreElements()) { - InetAddress ina = n.nextElement(); - byte[] adr = ina.getAddress(); - if (adr.length != 4) { - // TODO: Support IPv6 - // System.out.println("Dont support IPv6: " + ina); - continue; - } - /* Since we don't know actual broadcast address, - * lets try different combinations. - * Hopefully one of them will work. - */ - int h = adr[0] & 0xff; - if (h >= 1 && h <= 127 && h != 38) { - adr[3] = (byte)255; - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); - adr[2] = (byte)255; - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); - adr[1] = (byte)255; - } - else if (h >= 128 && h <= 191) { - adr[3] = (byte)255; - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); - adr[2] = (byte)255; + try { + Method m0 = f.getClass().getMethod("getInterfaceAddresses"); + for (Object ia : (List<?>)m0.invoke(f)) { + Method m1 = ia.getClass().getMethod("getBroadcast"); + socket.send(new DatagramPacket(buf, buf.length, (InetAddress)m1.invoke(ia), 1534)); } - else { - adr[3] = (byte)(adr[3] | 0x0f); - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); - adr[3] = (byte)(adr[3] | 0x01f); - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); - adr[3] = (byte)(adr[3] | 0x03f); - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); - adr[3] = (byte)(adr[3] | 0x07f); + } + catch (Exception x) { + Enumeration<InetAddress> n = f.getInetAddresses(); + while (n.hasMoreElements()) { + InetAddress ina = n.nextElement(); + byte[] adr = ina.getAddress(); + if (adr.length != 4) { + // TODO: Support IPv6 + // System.out.println("Don't support IPv6: " + ina); + continue; + } + /* Since we don't know actual broadcast address, + * lets try different combinations. + * Hopefully one of them will work. + */ + int h = adr[0] & 0xff; + if (h >= 1 && h <= 127 && h != 38) { + adr[3] = (byte)255; + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[2] = (byte)255; + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[1] = (byte)255; + } + else if (h >= 128 && h <= 191) { + adr[3] = (byte)255; + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[2] = (byte)255; + } + else { + adr[3] = (byte)(adr[3] | 0x0f); + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[3] = (byte)(adr[3] | 0x01f); + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[3] = (byte)(adr[3] | 0x03f); + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[3] = (byte)(adr[3] | 0x07f); + socket.send(new DatagramPacket(buf, buf.length, + InetAddress.getByAddress(null, adr), 1534)); + adr[3] = (byte)255; + } socket.send(new DatagramPacket(buf, buf.length, InetAddress.getByAddress(null, adr), 1534)); - adr[3] = (byte)255; } - socket.send(new DatagramPacket(buf, buf.length, - InetAddress.getByAddress(null, adr), 1534)); } } } diff --git a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/remote/RegistersProxy.java b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/remote/RegistersProxy.java index 42424c063..5c64aae33 100644 --- a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/remote/RegistersProxy.java +++ b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/internal/tcf/services/remote/RegistersProxy.java @@ -208,6 +208,37 @@ public class RegistersProxy implements IRegisters { }.token; } + public IToken getm(Location[] locs, final DoneGet done) { + return new Command(channel, this, "getm", new Object[]{ locs }) { + @SuppressWarnings("unchecked") + @Override + public void done(Exception error, Object[] args) { + byte[] val = null; + if (error == null) { + assert args.length == 3; + error = toError(args[0], args[1]); + String str = (String)args[2]; + if (str != null) val = Base64.toByteArray(str.toCharArray()); + } + done.doneGet(token, error, val); + } + }.token; + } + + public IToken setm(Location[] locs, byte[] value, final DoneSet done) { + return new Command(channel, this, "setm", + new Object[]{ locs, Base64.toBase64(value, 0, value.length) }) { + @Override + public void done(Exception error, Object[] args) { + if (error == null) { + assert args.length == 2; + error = toError(args[0], args[1]); + } + done.doneSet(token, error); + } + }.token; + } + public void addListener(final RegistersListener listener) { IChannel.IEventListener l = new IChannel.IEventListener() { @@ -294,4 +325,19 @@ public class RegistersProxy implements IRegisters { } return arr; } + + static { + JSON.addObjectWriter(Location.class, new JSON.ObjectWriter() { + public void write(Object o) throws IOException { + Location l = (Location)o; + JSON.write('['); + JSON.writeObject(l.id); + JSON.write(','); + JSON.writeUInt(l.offs); + JSON.write(','); + JSON.writeUInt(l.size); + JSON.write(']'); + } + }); + } } diff --git a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/protocol/JSON.java b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/protocol/JSON.java index e5dd10c61..f5000fed5 100644 --- a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/protocol/JSON.java +++ b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/protocol/JSON.java @@ -30,10 +30,29 @@ import org.eclipse.tm.internal.tcf.core.ReadOnlyMap; /** * JSON is TCF preferred marshaling. This class implements generation and parsing of JSON strings. - * The code is optimized for speed since it is a time-critical part of the framework. + * The code is optimized for speed since it is a time-critical part of the framework. + * + * Reading of JSON produces data structure that consists of objects of these classes: + * Boolean, Number, String, Collection, Map. + * + * Writing of JSON is supported for: + * Boolean, Number, String, char[], byte[], Object[], Collection, Map + * + * Clients can enable writing support for objects of a other classes by + * registering ObjectWriter interface implementation. */ public class JSON { + /** + * Clients implement ObjectWriter interface when they want to enable marshaling of + * object classes that are not directly supported by JSON library. + */ + public interface ObjectWriter { + void write(Object o) throws IOException; + } + + private static final Map<Class<?>,ObjectWriter> object_writers = new HashMap<Class<?>,ObjectWriter>(); + private static char[] tmp_buf = new char[0x1000]; private static byte[] tmp_bbf = new byte[0x1000]; private static int tmp_buf_pos; @@ -48,8 +67,22 @@ public class JSON { private static final char[] err_buf = new char[100]; private static int err_buf_pos; private static int err_buf_cnt; + + /** + * Add a handler for converting objects of a particular class into JSON. + * @param cls - a class + * @param writer - ObjectWriter implementation that provides generation of JSON for a given class. + */ + public static void addObjectWriter(Class<?> cls, ObjectWriter writer) { + object_writers.put(cls, writer); + } - private static void write(char ch) { + /** + * Write a character into JSON output buffer. + * Clients should not call this method directly, except from ObjectWriter implementation. + * @param ch + */ + public static void write(char ch) { if (tmp_buf_pos >= tmp_buf.length) { char[] tmp = new char[tmp_buf.length * 2]; System.arraycopy(tmp_buf, 0, tmp, 0, tmp_buf_pos); @@ -58,7 +91,13 @@ public class JSON { tmp_buf[tmp_buf_pos++] = ch; } - private static void write(String s) { + /** + * Write a string into JSON output buffer. + * The string is written "as-is". Call writeObject() to convert a String into JSON string. + * Clients should not call this method directly, except from ObjectWriter implementation. + * @param s - a string + */ + public static void write(String s) { int l = s.length(); for (int i = 0; i < l; i++) { char ch = s.charAt(i); @@ -67,7 +106,12 @@ public class JSON { } } - private static void writeUInt(int n) { + /** + * Write a non-negative integer number into JSON output buffer. + * Clients should not call this method directly, except from ObjectWriter implementation. + * @param n - a number + */ + public static void writeUInt(int n) { assert n >= 0; if (n >= 10) writeUInt(n / 10); write((char)('0' + n % 10)); @@ -320,8 +364,13 @@ public class JSON { return l.toArray(); } + /** + * Write an object into JSON output buffer. + * Clients should not call this method directly, except from ObjectWriter implementation. + * @param o - an object to write + */ @SuppressWarnings("unchecked") - private static void writeObject(Object o) throws IOException { + public static void writeObject(Object o) throws IOException { if (o == null) { write("null"); } @@ -419,7 +468,13 @@ public class JSON { write('}'); } else { - throw new IOException("JSON: unsupported object type"); + ObjectWriter writer = object_writers.get(o.getClass()); + if (writer != null) { + writer.write(o); + } + else { + throw new IOException("JSON: unsupported object type"); + } } } diff --git a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/services/IRegisters.java b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/services/IRegisters.java index 9fb1de145..73625f295 100644 --- a/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/services/IRegisters.java +++ b/plugins/org.eclipse.tm.tcf/src/org/eclipse/tm/tcf/services/IRegisters.java @@ -258,6 +258,43 @@ public interface IRegisters extends IService { } /** + * Read values of multiple locations in registers. + * @param locs - array of data locations. + * @param done - call back object. + * @return - pending command handle. + */ + IToken getm(Location[] locs, DoneGet done); + + /** + * Set values of multiple locations in registers. + * @param locs - array of data locations. + * @param value - value to write into the context. + * @param done - call back object. + * @return - pending command handle. + */ + IToken setm(Location[] locs, byte[] value, DoneSet done); + + /** + * Class Location represents value location in register context + */ + final class Location { + /** Register context ID */ + public final String id; + + /** offset in the context, in bytes */ + public final int offs; + + /** value size in byte */ + public final int size; + + public Location(String id, int offs, int size) { + this.id = id; + this.offs = offs; + this.size = size; + } + } + + /** * 'get' command call back interface. */ interface DoneGet { |