// ============================================================================= // Tesla.Contract — Pod RPC protocol (Console <-> Launcher Service) // ============================================================================= // The framed, JSON-based replacement for the old OFB-encrypted BinaryFormatter // RPC. Runs ON TOP of the same OFB-encrypted stream (NegotiateCryptoStreams is // unchanged); only the per-message serialization changed. // // Wire format, after the OFB/CONF handshake: // request : [4-byte little-endian length][UTF-8 JSON RpcRequest] // response: [4-byte little-endian length][UTF-8 JSON RpcResponse] // The InitiateInstallProduct out-of-band file transfer (8-byte length + raw // bytes) is unchanged and still follows its response frame on the same stream. // // Dispatch is by method NAME (RpcRequest.Method) — the old serialized-MethodBase // + SerializationBinder + MethodInfoProxy machinery is gone. Both ends share this // one file, so the request/response shape cannot drift. // ============================================================================= using System; using System.Collections.Generic; using System.IO; using System.Text.Json; namespace Tesla.Net { /// One RPC call: a method name plus its arguments as JSON elements. public sealed class RpcRequest { public string Method { get; set; } public List Args { get; set; } } /// One RPC result: the return value as JSON, or an error message. public sealed class RpcResponse { public JsonElement Result { get; set; } // JsonValueKind.Null for void / null public string Error { get; set; } // null on success } /// Framing + (de)serialization shared by the Console client and the /// Launcher Service. Synchronous to match the existing stream usage. public static class PodRpc { /// Upper bound on a single JSON frame (the bulk install payload is /// streamed out-of-band, not framed), guarding against hostile lengths. public const int MaxFrameBytes = 16 * 1024 * 1024; public static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { // The Tesla.Net wire types (LaunchData, LaunchPair, ...) expose public // FIELDS, which System.Text.Json ignores unless this is set. IncludeFields = true, }; // ── Framing ────────────────────────────────────────────────────────── public static void WriteFrame(Stream stream, byte[] payload) { if (payload.Length > MaxFrameBytes) throw new IOException($"RPC frame too large ({payload.Length} bytes)."); var len = BitConverter.GetBytes(payload.Length); // little-endian on x86/x64 stream.Write(len, 0, 4); stream.Write(payload, 0, payload.Length); stream.Flush(); } public static byte[] ReadFrame(Stream stream) { var lenBuf = ReadExact(stream, 4); int len = BitConverter.ToInt32(lenBuf, 0); if (len < 0 || len > MaxFrameBytes) throw new IOException($"RPC frame length out of range ({len})."); return ReadExact(stream, len); } private static byte[] ReadExact(Stream stream, int count) { var buf = new byte[count]; int off = 0; while (off < count) { int n = stream.Read(buf, off, count - off); if (n == 0) throw new EndOfStreamException("Connection closed mid-frame."); off += n; } return buf; } // ── Request ────────────────────────────────────────────────────────── public static void WriteRequest(Stream stream, string method, object[] args) { var req = new RpcRequest { Method = method, Args = new List() }; if (args != null) foreach (var a in args) req.Args.Add(JsonSerializer.SerializeToElement(a, JsonOptions)); WriteFrame(stream, JsonSerializer.SerializeToUtf8Bytes(req, JsonOptions)); } public static RpcRequest ReadRequest(Stream stream) => JsonSerializer.Deserialize(ReadFrame(stream), JsonOptions); // ── Response ───────────────────────────────────────────────────────── public static void WriteResponse(Stream stream, object result, string error) { var resp = new RpcResponse { // Always a valid element (JSON null when there is no result): a // default(JsonElement) is ValueKind.Undefined and is not serializable. Result = JsonSerializer.SerializeToElement(error == null ? result : null, JsonOptions), Error = error, }; WriteFrame(stream, JsonSerializer.SerializeToUtf8Bytes(resp, JsonOptions)); } public static RpcResponse ReadResponse(Stream stream) => JsonSerializer.Deserialize(ReadFrame(stream), JsonOptions); } }