This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch feat/e-wl-proxy-planb
in repository enlightenment.

View the commit online.

commit eb0c253f550410f03331f47d1623e70c2fff2288
Author: [email protected] <[email protected]>
AuthorDate: Sat Jun 13 11:32:11 2026 -0600

    feat(e_wl_proxy): object-id remapping and message translation
    
    After the compositor restarts it assigns different object ids than the ones
    the client holds, so the proxy keeps a bidirectional client<->backend id map
    with lowest-free allocators (e_wl_proxy_idmap) and rewrites every forwarded
    message in place to translate the header object id, object-typed arguments and
    new_id allocations (e_wl_proxy_xlate), mutating the map as objects are created
    and destroyed.
    
    The allocator honors libwayland's dense-id rule (a client-allocated new_id
    must be the next free slot) and never reuses an id until the server confirms
    destruction via wl_display.delete_id, mirroring libwayland-client exactly so
    the restarted compositor accepts the replayed and ongoing traffic.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
---
 src/bin/e_wl_proxy/e_wl_proxy_idmap.c | 232 ++++++++++++++++++++++++++++++++++
 src/bin/e_wl_proxy/e_wl_proxy_idmap.h |  55 ++++++++
 src/bin/e_wl_proxy/e_wl_proxy_xlate.c | 147 +++++++++++++++++++++
 src/bin/e_wl_proxy/e_wl_proxy_xlate.h |  28 ++++
 4 files changed, 462 insertions(+)

diff --git a/src/bin/e_wl_proxy/e_wl_proxy_idmap.c b/src/bin/e_wl_proxy/e_wl_proxy_idmap.c
new file mode 100644
index 000000000..f16784a9a
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_idmap.c
@@ -0,0 +1,232 @@
+#include "config.h"
+#include "e_wl_proxy_idmap.h"
+#include "e_wl_proxy_objmap.h"
+#include "e_wl_proxy_protocol.h"
+#include <stdlib.h>
+#include <string.h>
+
+typedef struct _Ent
+{
+   uint32_t                   client_id;
+   uint32_t                   backend_id;
+   const struct wl_interface *iface;
+} Ent;
+
+/* lowest-free allocator: a `next` counter plus a free list of returned ids. */
+typedef struct _Alloc
+{
+   uint32_t  next;     /* next never-used id */
+   uint32_t *free;     /* returned ids, unsorted */
+   int       nfree;
+   int       cap;
+} Alloc;
+
+struct _E_Wl_Proxy_Idmap
+{
+   E_Wl_Proxy_Objmap *by_client;   /* client_id -> Ent* */
+   E_Wl_Proxy_Objmap *by_backend;  /* backend_id -> Ent* */
+   unsigned int       count;
+   Alloc              client_range; /* backend ids in [2 .. server_start) */
+   Alloc              server_range; /* client-facing ids in [server_start ..) */
+};
+
+static uint32_t
+_alloc_get(Alloc *a)
+{
+   if (a->nfree > 0)
+     {
+        int i, lo = 0;
+        for (i = 1; i < a->nfree; i++) if (a->free[i] < a->free[lo]) lo = i;
+        uint32_t id = a->free[lo];
+        a->free[lo] = a->free[a->nfree - 1];   /* swap-remove */
+        a->nfree--;
+        return id;
+     }
+   return a->next++;
+}
+
+static void
+_alloc_put(Alloc *a, uint32_t id)
+{
+   if (id + 1 == a->next) { a->next--; return; }   /* trim the tail cheaply */
+   if (a->nfree == a->cap)
+     {
+        int ncap = a->cap ? a->cap * 2 : 16;
+        uint32_t *nf = realloc(a->free, ncap * sizeof(uint32_t));
+        if (!nf) return;   /* leak the id (it just won't be reused); harmless */
+        a->free = nf; a->cap = ncap;
+     }
+   a->free[a->nfree++] = id;
+}
+
+static Ent *
+_ent_new(uint32_t cid, uint32_t bid, const struct wl_interface *iface)
+{
+   Ent *e = malloc(sizeof(*e));
+   if (!e) return NULL;
+   e->client_id = cid; e->backend_id = bid; e->iface = iface;
+   return e;
+}
+
+E_Wl_Proxy_Idmap *
+e_wl_proxy_idmap_new(void)
+{
+   E_Wl_Proxy_Idmap *m = calloc(1, sizeof(*m));
+   Ent *disp;
+   if (!m) return NULL;
+   m->by_client = e_wl_proxy_objmap_new();
+   m->by_backend = e_wl_proxy_objmap_new();
+   if (!m->by_client || !m->by_backend)
+     {
+        e_wl_proxy_objmap_free(m->by_client, NULL);
+        e_wl_proxy_objmap_free(m->by_backend, NULL);
+        free(m);
+        return NULL;
+     }
+   m->client_range.next = 2;
+   m->server_range.next = E_WL_PROXY_SERVER_ID_START;
+   disp = _ent_new(1, 1, e_wl_proxy_iface_display());
+   if (!disp) { e_wl_proxy_idmap_free(m); return NULL; }
+   e_wl_proxy_objmap_set(m->by_client, 1, disp);
+   e_wl_proxy_objmap_set(m->by_backend, 1, disp);
+   m->count = 1;
+   return m;
+}
+
+void
+e_wl_proxy_idmap_free(E_Wl_Proxy_Idmap *m)
+{
+   if (!m) return;
+   /* free the Ent objects once, via the client index; clear the backend index
+    * without a second free to avoid a double free of the shared Ents. */
+   e_wl_proxy_objmap_free(m->by_client, free);
+   e_wl_proxy_objmap_free(m->by_backend, NULL);
+   free(m->client_range.free);
+   free(m->server_range.free);
+   free(m);
+}
+
+unsigned int
+e_wl_proxy_idmap_count(E_Wl_Proxy_Idmap *m) { return m ? m->count : 0; }
+
+uint32_t
+e_wl_proxy_idmap_alloc_backend(E_Wl_Proxy_Idmap *m)
+{
+   return m ? _alloc_get(&m->client_range) : 0;
+}
+
+void
+e_wl_proxy_idmap_free_backend(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+   if (m && backend_id >= 2 && backend_id < E_WL_PROXY_SERVER_ID_START)
+     _alloc_put(&m->client_range, backend_id);
+}
+
+static void
+_del_ent(E_Wl_Proxy_Idmap *m, Ent *e)
+{
+   if (!e) return;
+   e_wl_proxy_objmap_del(m->by_client, e->client_id);
+   e_wl_proxy_objmap_del(m->by_backend, e->backend_id);
+   if (e->backend_id >= E_WL_PROXY_SERVER_ID_START)
+     _alloc_put(&m->server_range, e->client_id);   /* server object: free client id */
+   else if (e->backend_id >= 2)
+     _alloc_put(&m->client_range, e->backend_id);   /* client object: free backend id */
+   if (m->count) m->count--;
+   free(e);
+}
+
+/* Remove a mapping WITHOUT returning its allocated id to the free list. Used on
+ * id reuse: if the client reuses an id whose old object the backend still holds
+ * live (a tree-retained object recreated during replay, whose client-side
+ * destroy we already processed pre-crash), recycling that backend id would make
+ * the new object collide with the live old one in the backend ("invalid
+ * arguments"). Leaking the id keeps our free set a subset of the backend's. The
+ * leak is bounded (one per initially-retained-dead id) and reset each recovery. */
+static void
+_unmap_ent(E_Wl_Proxy_Idmap *m, Ent *e)
+{
+   if (!e) return;
+   e_wl_proxy_objmap_del(m->by_client, e->client_id);
+   e_wl_proxy_objmap_del(m->by_backend, e->backend_id);
+   if (m->count) m->count--;
+   free(e);
+}
+
+uint32_t
+e_wl_proxy_idmap_add_client(E_Wl_Proxy_Idmap *m, uint32_t client_id,
+                            const struct wl_interface *iface)
+{
+   Ent *old, *e;
+   uint32_t bid;
+   if (!m) return 0;
+   old = e_wl_proxy_objmap_get(m->by_client, client_id);
+   if (old) _unmap_ent(m, old);   /* reuse: leak old id, backend may still hold it */
+   bid = _alloc_get(&m->client_range);
+   e = _ent_new(client_id, bid, iface);
+   if (!e) { _alloc_put(&m->client_range, bid); return 0; }
+   e_wl_proxy_objmap_set(m->by_client, client_id, e);
+   e_wl_proxy_objmap_set(m->by_backend, bid, e);
+   m->count++;
+   return bid;
+}
+
+uint32_t
+e_wl_proxy_idmap_add_server(E_Wl_Proxy_Idmap *m, uint32_t backend_id,
+                            const struct wl_interface *iface)
+{
+   Ent *old, *e;
+   uint32_t cid;
+   if (!m) return 0;
+   old = e_wl_proxy_objmap_get(m->by_backend, backend_id);
+   if (old) _unmap_ent(m, old);   /* reuse: leak old client id */
+   cid = _alloc_get(&m->server_range);
+   e = _ent_new(cid, backend_id, iface);
+   if (!e) { _alloc_put(&m->server_range, cid); return 0; }
+   e_wl_proxy_objmap_set(m->by_client, cid, e);
+   e_wl_proxy_objmap_set(m->by_backend, backend_id, e);
+   m->count++;
+   return cid;
+}
+
+uint32_t
+e_wl_proxy_idmap_c2b(E_Wl_Proxy_Idmap *m, uint32_t client_id)
+{
+   Ent *e = m ? e_wl_proxy_objmap_get(m->by_client, client_id) : NULL;
+   return e ? e->backend_id : 0;
+}
+
+uint32_t
+e_wl_proxy_idmap_b2c(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+   Ent *e = m ? e_wl_proxy_objmap_get(m->by_backend, backend_id) : NULL;
+   return e ? e->client_id : 0;
+}
+
+const struct wl_interface *
+e_wl_proxy_idmap_iface_c(E_Wl_Proxy_Idmap *m, uint32_t client_id)
+{
+   Ent *e = m ? e_wl_proxy_objmap_get(m->by_client, client_id) : NULL;
+   return e ? e->iface : NULL;
+}
+
+const struct wl_interface *
+e_wl_proxy_idmap_iface_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+   Ent *e = m ? e_wl_proxy_objmap_get(m->by_backend, backend_id) : NULL;
+   return e ? e->iface : NULL;
+}
+
+void
+e_wl_proxy_idmap_del_c(E_Wl_Proxy_Idmap *m, uint32_t client_id)
+{
+   if (!m || client_id == 1) return;   /* never unmap wl_display */
+   _del_ent(m, e_wl_proxy_objmap_get(m->by_client, client_id));
+}
+
+void
+e_wl_proxy_idmap_del_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id)
+{
+   if (!m || backend_id == 1) return;
+   _del_ent(m, e_wl_proxy_objmap_get(m->by_backend, backend_id));
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_idmap.h b/src/bin/e_wl_proxy/e_wl_proxy_idmap.h
new file mode 100644
index 000000000..484d6ba40
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_idmap.h
@@ -0,0 +1,55 @@
+#ifndef E_WL_PROXY_IDMAP_H
+#define E_WL_PROXY_IDMAP_H
+
+#include <stdint.h>
+#include <wayland-util.h>   /* struct wl_interface */
+
+/* Server-allocated object ids start here (libwayland WL_SERVER_ID_START). */
+#define E_WL_PROXY_SERVER_ID_START 0xff000000u
+
+/* A per-connection bidirectional map between client-space ids (what the app
+ * uses; stable for the connection) and backend-space ids (what the current E
+ * connection sees; rebuilt every recovery). Lowest-free allocation in each
+ * space mirrors libwayland so the backend id stream stays dense and gapless. */
+typedef struct _E_Wl_Proxy_Idmap E_Wl_Proxy_Idmap;
+
+/* New map, pre-seeded with wl_display (client 1 <-> backend 1). NULL on OOM. */
+E_Wl_Proxy_Idmap *e_wl_proxy_idmap_new(void);
+void              e_wl_proxy_idmap_free(E_Wl_Proxy_Idmap *m);
+
+/* test/query: number of live mappings (incl. the seeded wl_display). */
+unsigned int e_wl_proxy_idmap_count(E_Wl_Proxy_Idmap *m);
+
+/* Low-level allocators (lowest freed id first). alloc_backend hands out a
+ * client-range backend id; free_backend returns one to the pool. Used for the
+ * replay sync-barrier callback, which needs a real gapless backend id but no
+ * client-space mapping. Returns 0 on OOM. */
+uint32_t e_wl_proxy_idmap_alloc_backend(E_Wl_Proxy_Idmap *m);
+void     e_wl_proxy_idmap_free_backend(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+/* Client created object `client_id` of `iface`: allocate a fresh lowest-free
+ * backend id (client range), record the mapping, return the backend id (0 on
+ * OOM). If `client_id` is already mapped, the old mapping is dropped first. */
+uint32_t e_wl_proxy_idmap_add_client(E_Wl_Proxy_Idmap *m, uint32_t client_id,
+                                     const struct wl_interface *iface);
+
+/* Server created object `backend_id` (server range) of `iface`: allocate a
+ * fresh lowest-free client-facing id (server range), record, return it (0 on
+ * OOM). If `backend_id` is already mapped, the old mapping is dropped first. */
+uint32_t e_wl_proxy_idmap_add_server(E_Wl_Proxy_Idmap *m, uint32_t backend_id,
+                                     const struct wl_interface *iface);
+
+/* Translate. Return 0 if the id is not mapped (caller treats as error/drop). */
+uint32_t e_wl_proxy_idmap_c2b(E_Wl_Proxy_Idmap *m, uint32_t client_id);
+uint32_t e_wl_proxy_idmap_b2c(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+/* Interface of a mapped object (for signature walking). NULL if not mapped. */
+const struct wl_interface *e_wl_proxy_idmap_iface_c(E_Wl_Proxy_Idmap *m, uint32_t client_id);
+const struct wl_interface *e_wl_proxy_idmap_iface_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+/* Destroy a mapping by either id; the freed backend/client id returns to its
+ * allocator pool (so it is reused, mirroring libwayland). No-op if absent. */
+void e_wl_proxy_idmap_del_c(E_Wl_Proxy_Idmap *m, uint32_t client_id);
+void e_wl_proxy_idmap_del_b(E_Wl_Proxy_Idmap *m, uint32_t backend_id);
+
+#endif
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_xlate.c b/src/bin/e_wl_proxy/e_wl_proxy_xlate.c
new file mode 100644
index 000000000..5ba337617
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_xlate.c
@@ -0,0 +1,147 @@
+#include "config.h"
+#include "e_wl_proxy_xlate.h"
+#include "e_wl_proxy_wire.h"
+#include "e_wl_proxy_protocol.h"
+#include <string.h>
+
+static inline size_t _pad4(size_t n) { return (n + 3) & ~((size_t)3); }
+
+int
+e_wl_proxy_xlate(unsigned char *msg, size_t len, E_Wl_Proxy_Idmap *map,
+                 Eina_Bool is_request)
+{
+   E_Wl_Proxy_Hdr hdr;
+   const struct wl_interface *sender_iface;
+   const struct wl_message *m;
+   unsigned char *body;
+   const unsigned char *last_str = NULL;
+   uint32_t last_str_len = 0;
+   size_t off, body_len;
+   uint32_t sender_b;          /* translated sender id */
+   const char *sig, *c;
+   int arg = 0;
+
+   if (e_wl_proxy_wire_hdr(msg, len, &hdr) != 1) return -1;
+   if (hdr.size < 8 || hdr.size > len) return -1;
+
+   /* resolve sender interface + translate the header object_id */
+   sender_iface = is_request ? e_wl_proxy_idmap_iface_c(map, hdr.object_id)
+                             : e_wl_proxy_idmap_iface_b(map, hdr.object_id);
+   if (!sender_iface) return -1;
+   sender_b = is_request ? e_wl_proxy_idmap_c2b(map, hdr.object_id)
+                         : e_wl_proxy_idmap_b2c(map, hdr.object_id);
+   if (!sender_b) return -1;
+   memcpy(msg, &sender_b, 4);
+
+   if (is_request)
+     {
+        if (hdr.opcode >= (uint16_t)sender_iface->method_count) return -1;
+        m = &sender_iface->methods[hdr.opcode];
+     }
+   else
+     {
+        if (hdr.opcode >= (uint16_t)sender_iface->event_count) return 0; /* unknown event: pass */
+        m = &sender_iface->events[hdr.opcode];
+     }
+
+   body = msg + 8;
+   body_len = (size_t)hdr.size - 8;
+   off = 0;
+
+   /* wl_display.delete_id(uint id): the id rides a 'u', not an 'o'. Special-case
+    * the backend->client direction: translate b->c, then unmap. */
+   if (!is_request && hdr.object_id == 1 && !strcmp(m->name, "delete_id"))
+     {
+        uint32_t freed_b, freed_c;
+        if (body_len < 4) return -1;
+        memcpy(&freed_b, body, 4);
+        freed_c = e_wl_proxy_idmap_b2c(map, freed_b);
+        if (freed_c) { memcpy(body, &freed_c, 4); e_wl_proxy_idmap_del_b(map, freed_b); }
+        return 0;
+     }
+
+   for (sig = m->signature, c = sig; *c; c++)
+     {
+        if (*c >= '0' && *c <= '9') continue;
+        if (*c == '?') continue;
+
+        switch (*c)
+          {
+           case 'i': case 'u': case 'f':
+             if (off + 4 > body_len) return -1;
+             off += 4;
+             break;
+           case 'o':
+             {
+                uint32_t oid, t;
+                if (off + 4 > body_len) return -1;
+                memcpy(&oid, body + off, 4);
+                if (oid)
+                  {
+                     t = is_request ? e_wl_proxy_idmap_c2b(map, oid)
+                                    : e_wl_proxy_idmap_b2c(map, oid);
+                     if (!t) return -1;                 /* unmapped ref: drop msg */
+                     memcpy(body + off, &t, 4);
+                  }
+                off += 4;
+             }
+             break;
+           case 'n':
+             {
+                uint32_t nid, t;
+                const struct wl_interface *iface = m->types[arg];
+                if (off + 4 > body_len) return -1;
+                memcpy(&nid, body + off, 4);
+                if (!iface)
+                  {
+                     /* generic new_id (bind): interface is the preceding 's' */
+                     if (last_str && last_str_len > 0)
+                       {
+                          char name[64];
+                          size_t cp = last_str_len - 1;       /* drop NUL */
+                          if (cp >= sizeof(name)) cp = sizeof(name) - 1;
+                          memcpy(name, last_str, cp); name[cp] = '\0';
+                          iface = e_wl_proxy_iface_by_name(name);
+                       }
+                  }
+                if (is_request)
+                  t = e_wl_proxy_idmap_add_client(map, nid, iface);
+                else
+                  t = iface ? e_wl_proxy_idmap_add_server(map, nid, iface) : 0;
+                if (!t) return -1;
+                memcpy(body + off, &t, 4);
+                off += 4;
+                arg++;
+                continue;
+             }
+           case 's': case 'a':
+             {
+                uint32_t alen;
+                if (off + 4 > body_len) return -1;
+                memcpy(&alen, body + off, 4); off += 4;
+                if (alen > body_len - off) return -1;
+                if (off + _pad4(alen) > body_len) return -1;
+                if (*c == 's') { last_str = body + off; last_str_len = alen; }
+                off += _pad4(alen);
+             }
+             break;
+           case 'h':
+             break;                               /* fd: no body bytes */
+           default:
+             return -1;
+          }
+        arg++;
+     }
+
+   /* NOTE: a destructor request does NOT free the backend id here. Wayland
+    * forbids reusing an object id until the server confirms destruction via
+    * wl_display.delete_id; the backend object is still live until E processes
+    * the destroy. If we freed the id now, a later client object could reuse it
+    * before E's delete_id arrived, and that delete_id (for the old object) would
+    * then clobber the new mapping — dropping the new object's events (observed:
+    * a region destroyed, its id reused for a frame callback, the callback's
+    * "done" then untranslatable). The mapping is freed when delete_id arrives
+    * (handled above, backend->client), exactly mirroring libwayland-client. */
+
+   return 0;
+}
diff --git a/src/bin/e_wl_proxy/e_wl_proxy_xlate.h b/src/bin/e_wl_proxy/e_wl_proxy_xlate.h
new file mode 100644
index 000000000..8c997628a
--- /dev/null
+++ b/src/bin/e_wl_proxy/e_wl_proxy_xlate.h
@@ -0,0 +1,28 @@
+#ifndef E_WL_PROXY_XLATE_H
+#define E_WL_PROXY_XLATE_H
+
+#include <stddef.h>
+#include <Eina.h>
+#include "e_wl_proxy_idmap.h"
+
+/* Translate one complete wire message (8-byte header + body) in place, rewriting
+ * every id-bearing field between client and backend space via `map`, and
+ * applying the implied map mutation:
+ *   - a new_id arg adds a mapping (client->backend for a request, server->client
+ *     for an event) and the field is rewritten to the freshly allocated id;
+ *   - a `destroy` request or wl_display.delete_id event removes the mapping and
+ *     returns the id to its allocator pool.
+ *
+ * `is_request` selects direction: EINA_TRUE = client->backend (rewrite c->b,
+ * walk methods[opcode]); EINA_FALSE = backend->client (rewrite b->c, walk
+ * events[opcode]). The message length never changes (ids are fixed 4-byte
+ * fields), so `msg` is rewritten in its existing `len` bytes.
+ *
+ * Returns 0 on success. Returns -1 if the sender or a referenced object is not
+ * mapped, the opcode is out of range, or the body is malformed: the caller
+ * should DROP this message (during replay: skip the setter; during live
+ * forwarding: log and drop, the object is already gone). */
+int e_wl_proxy_xlate(unsigned char *msg, size_t len,
+                     E_Wl_Proxy_Idmap *map, Eina_Bool is_request);
+
+#endif

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to