1 /*
2 * ga-entry-group.c - Source for GaEntryGroup
3 * Copyright (C) 2006-2007 Collabora Ltd.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include "avahi-common/avahi-malloc.h"
29
30 #include "ga-error.h"
31 #include "ga-entry-group.h"
32 #include "ga-entry-group-enumtypes.h"
33
34 G_DEFINE_TYPE(GaEntryGroup, ga_entry_group, G_TYPE_OBJECT)
35
36 static void _free_service(gpointer data);
37
38 /* signal enum */
39 enum {
40 STATE_CHANGED,
41 LAST_SIGNAL
42 };
43
44 static guint signals[LAST_SIGNAL] = { 0 };
45
46 /* properties */
47 enum {
48 PROP_STATE = 1
49 };
50
51 /* private structures */
52 typedef struct _GaEntryGroupPrivate GaEntryGroupPrivate;
53
54 struct _GaEntryGroupPrivate {
55 GaEntryGroupState state;
56 GaClient *client;
57 AvahiEntryGroup *group;
58 GHashTable *services;
59 gboolean dispose_has_run;
60 };
61
62 typedef struct _GaEntryGroupServicePrivate GaEntryGroupServicePrivate;
63
64 struct _GaEntryGroupServicePrivate {
65 GaEntryGroupService public;
66 GaEntryGroup *group;
67 gboolean frozen;
68 GHashTable *entries;
69 };
70
71 typedef struct _GaEntryGroupServiceEntry GaEntryGroupServiceEntry;
72
73 struct _GaEntryGroupServiceEntry {
74 guint8 *value;
75 gsize size;
76 };
77
78
79 #define GA_ENTRY_GROUP_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GA_TYPE_ENTRY_GROUP, GaEntryGroupPrivate))
80
ga_entry_group_init(GaEntryGroup * obj)81 static void ga_entry_group_init(GaEntryGroup * obj) {
82 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(obj);
83 /* allocate any data required by the object here */
84 priv->state = GA_ENTRY_GROUP_STATE_UNCOMMITED;
85 priv->client = NULL;
86 priv->group = NULL;
87 priv->services = g_hash_table_new_full(g_direct_hash,
88 g_direct_equal,
89 NULL, _free_service);
90 }
91
92 static void ga_entry_group_dispose(GObject * object);
93 static void ga_entry_group_finalize(GObject * object);
94
ga_entry_group_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)95 static void ga_entry_group_get_property(GObject * object,
96 guint property_id,
97 GValue * value, GParamSpec * pspec) {
98 GaEntryGroup *group = GA_ENTRY_GROUP(object);
99 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
100
101 switch (property_id) {
102 case PROP_STATE:
103 g_value_set_enum(value, priv->state);
104 break;
105 default:
106 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
107 break;
108 }
109 }
110
ga_entry_group_class_init(GaEntryGroupClass * ga_entry_group_class)111 static void ga_entry_group_class_init(GaEntryGroupClass * ga_entry_group_class) {
112 GObjectClass *object_class = G_OBJECT_CLASS(ga_entry_group_class);
113 GParamSpec *param_spec;
114
115 g_type_class_add_private(ga_entry_group_class,
116 sizeof (GaEntryGroupPrivate));
117
118 object_class->dispose = ga_entry_group_dispose;
119 object_class->finalize = ga_entry_group_finalize;
120 object_class->get_property = ga_entry_group_get_property;
121
122 param_spec = g_param_spec_enum("state", "Entry Group state",
123 "The state of the avahi entry group",
124 GA_TYPE_ENTRY_GROUP_STATE,
125 GA_ENTRY_GROUP_STATE_UNCOMMITED,
126 G_PARAM_READABLE |
127 G_PARAM_STATIC_NAME |
128 G_PARAM_STATIC_BLURB);
129 g_object_class_install_property(object_class, PROP_STATE, param_spec);
130
131 signals[STATE_CHANGED] =
132 g_signal_new("state-changed",
133 G_OBJECT_CLASS_TYPE(ga_entry_group_class),
134 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
135 0,
136 NULL, NULL,
137 g_cclosure_marshal_VOID__ENUM,
138 G_TYPE_NONE, 1, GA_TYPE_ENTRY_GROUP_STATE);
139 }
140
ga_entry_group_dispose(GObject * object)141 void ga_entry_group_dispose(GObject * object) {
142 GaEntryGroup *self = GA_ENTRY_GROUP(object);
143 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
144
145 if (priv->dispose_has_run)
146 return;
147 priv->dispose_has_run = TRUE;
148
149 /* release any references held by the object here */
150 if (priv->group) {
151 avahi_entry_group_free(priv->group);
152 priv->group = NULL;
153 }
154
155 if (priv->client) {
156 g_object_unref(priv->client);
157 priv->client = NULL;
158 }
159
160 if (G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose)
161 G_OBJECT_CLASS(ga_entry_group_parent_class)->dispose(object);
162 }
163
ga_entry_group_finalize(GObject * object)164 void ga_entry_group_finalize(GObject * object) {
165 GaEntryGroup *self = GA_ENTRY_GROUP(object);
166 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
167
168 /* free any data held directly by the object here */
169 g_hash_table_destroy(priv->services);
170 priv->services = NULL;
171
172 G_OBJECT_CLASS(ga_entry_group_parent_class)->finalize(object);
173 }
174
_free_service(gpointer data)175 static void _free_service(gpointer data) {
176 GaEntryGroupService *s = (GaEntryGroupService *) data;
177 GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) s;
178 g_free(s->name);
179 g_free(s->type);
180 g_free(s->domain);
181 g_free(s->host);
182 g_hash_table_destroy(p->entries);
183 g_free(s);
184 }
185
detail_for_state(AvahiEntryGroupState state)186 static GQuark detail_for_state(AvahiEntryGroupState state) {
187 static struct {
188 AvahiEntryGroupState state;
189 const gchar *name;
190 GQuark quark;
191 } states[] = {
192 { AVAHI_ENTRY_GROUP_UNCOMMITED, "uncommited", 0},
193 { AVAHI_ENTRY_GROUP_REGISTERING, "registering", 0},
194 { AVAHI_ENTRY_GROUP_ESTABLISHED, "established", 0},
195 { AVAHI_ENTRY_GROUP_COLLISION, "collistion", 0},
196 { AVAHI_ENTRY_GROUP_FAILURE, "failure", 0},
197 { 0, NULL, 0}
198 };
199 int i;
200
201 for (i = 0; states[i].name != NULL; i++) {
202 if (state != states[i].state)
203 continue;
204
205 if (!states[i].quark)
206 states[i].quark = g_quark_from_static_string(states[i].name);
207 return states[i].quark;
208 }
209 g_assert_not_reached();
210 }
211
_avahi_entry_group_cb(AvahiEntryGroup * g,AvahiEntryGroupState state,void * data)212 static void _avahi_entry_group_cb(AvahiEntryGroup * g,
213 AvahiEntryGroupState state, void *data) {
214 GaEntryGroup *self = GA_ENTRY_GROUP(data);
215 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(self);
216
217 /* Avahi can call the callback before return from _client_new */
218 if (priv->group == NULL)
219 priv->group = g;
220
221 g_assert(g == priv->group);
222 priv->state = state;
223 g_signal_emit(self, signals[STATE_CHANGED],
224 detail_for_state(state), state);
225 }
226
ga_entry_group_new(void)227 GaEntryGroup *ga_entry_group_new(void) {
228 return g_object_new(GA_TYPE_ENTRY_GROUP, NULL);
229 }
230
_entry_hash(gconstpointer v)231 static guint _entry_hash(gconstpointer v) {
232 const GaEntryGroupServiceEntry *entry =
233 (const GaEntryGroupServiceEntry *) v;
234 guint32 h = 0;
235 guint i;
236
237 for (i = 0; i < entry->size; i++) {
238 h = (h << 5) - h + entry->value[i];
239 }
240
241 return h;
242 }
243
_entry_equal(gconstpointer a,gconstpointer b)244 static gboolean _entry_equal(gconstpointer a, gconstpointer b) {
245 const GaEntryGroupServiceEntry *aentry =
246 (const GaEntryGroupServiceEntry *) a;
247 const GaEntryGroupServiceEntry *bentry =
248 (const GaEntryGroupServiceEntry *) b;
249
250 if (aentry->size != bentry->size) {
251 return FALSE;
252 }
253
254 return memcmp(aentry->value, bentry->value, aentry->size) == 0;
255 }
256
_new_entry(const guint8 * value,gsize size)257 static GaEntryGroupServiceEntry *_new_entry(const guint8 * value, gsize size) {
258 GaEntryGroupServiceEntry *entry;
259
260 if (value == NULL) {
261 return NULL;
262 }
263
264 entry = g_slice_new(GaEntryGroupServiceEntry);
265 entry->value = g_malloc(size + 1);
266 memcpy(entry->value, value, size);
267 /* for string keys, make sure it's NUL-terminated too */
268 entry->value[size] = 0;
269 entry->size = size;
270
271 return entry;
272 }
273
_set_entry(GHashTable * table,const guint8 * key,gsize ksize,const guint8 * value,gsize vsize)274 static void _set_entry(GHashTable * table, const guint8 * key, gsize ksize,
275 const guint8 * value, gsize vsize) {
276
277 g_hash_table_insert(table, _new_entry(key, ksize),
278 _new_entry(value, vsize));
279 }
280
_free_entry(gpointer data)281 static void _free_entry(gpointer data) {
282 GaEntryGroupServiceEntry *entry = (GaEntryGroupServiceEntry *) data;
283
284 if (entry == NULL) {
285 return;
286 }
287
288 g_free(entry->value);
289 g_slice_free(GaEntryGroupServiceEntry, entry);
290 }
291
_string_list_to_hash(AvahiStringList * list)292 static GHashTable *_string_list_to_hash(AvahiStringList * list) {
293 GHashTable *ret;
294 AvahiStringList *t;
295
296 ret = g_hash_table_new_full(_entry_hash,
297 _entry_equal, _free_entry, _free_entry);
298
299 for (t = list; t != NULL; t = avahi_string_list_get_next(t)) {
300 gchar *key;
301 gchar *value;
302 gsize size;
303 int r;
304
305 /* list_get_pair only fails if if memory allocation fails. Normal glib
306 * behaviour is to assert/abort when that happens */
307 r = avahi_string_list_get_pair(t, &key, &value, &size);
308 g_assert(r == 0);
309
310 if (value == NULL) {
311 _set_entry(ret, t->text, t->size, NULL, 0);
312 } else {
313 _set_entry(ret, (const guint8 *) key, strlen(key),
314 (const guint8 *) value, size);
315 }
316 avahi_free(key);
317 avahi_free(value);
318 }
319 return ret;
320 }
321
_hash_to_string_list_foreach(gpointer key,gpointer value,gpointer data)322 static void _hash_to_string_list_foreach(gpointer key, gpointer value, gpointer data) {
323 AvahiStringList **list = (AvahiStringList **) data;
324 GaEntryGroupServiceEntry *kentry = (GaEntryGroupServiceEntry *) key;
325 GaEntryGroupServiceEntry *ventry = (GaEntryGroupServiceEntry *) value;
326
327 if (value != NULL) {
328 *list = avahi_string_list_add_pair_arbitrary(*list,
329 (gchar *) kentry->value,
330 ventry->value,
331 ventry->size);
332 } else {
333 *list = avahi_string_list_add_arbitrary(*list,
334 kentry->value, kentry->size);
335 }
336 }
337
_hash_to_string_list(GHashTable * table)338 static AvahiStringList *_hash_to_string_list(GHashTable * table) {
339 AvahiStringList *list = NULL;
340 g_hash_table_foreach(table, _hash_to_string_list_foreach,
341 (gpointer) & list);
342 return list;
343 }
344
ga_entry_group_add_service_strlist(GaEntryGroup * group,const gchar * name,const gchar * type,guint16 port,GError ** error,AvahiStringList * txt)345 GaEntryGroupService *ga_entry_group_add_service_strlist(GaEntryGroup * group,
346 const gchar * name,
347 const gchar * type,
348 guint16 port,
349 GError ** error,
350 AvahiStringList *
351 txt) {
352 return ga_entry_group_add_service_full_strlist(group, AVAHI_IF_UNSPEC,
353 AVAHI_PROTO_UNSPEC, 0,
354 name, type, NULL, NULL,
355 port, error, txt);
356 }
357
ga_entry_group_add_service_full_strlist(GaEntryGroup * group,AvahiIfIndex interface,AvahiProtocol protocol,AvahiPublishFlags flags,const gchar * name,const gchar * type,const gchar * domain,const gchar * host,guint16 port,GError ** error,AvahiStringList * txt)358 GaEntryGroupService *ga_entry_group_add_service_full_strlist(GaEntryGroup *
359 group,
360 AvahiIfIndex
361 interface,
362 AvahiProtocol
363 protocol,
364 AvahiPublishFlags
365 flags,
366 const gchar *
367 name,
368 const gchar *
369 type,
370 const gchar *
371 domain,
372 const gchar *
373 host,
374 guint16 port,
375 GError ** error,
376 AvahiStringList *
377 txt) {
378 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
379 GaEntryGroupServicePrivate *service = NULL;
380 int ret;
381
382 ret = avahi_entry_group_add_service_strlst(priv->group,
383 interface, protocol,
384 flags,
385 name, type,
386 domain, host, port, txt);
387 if (ret) {
388 if (error != NULL) {
389 *error = g_error_new(GA_ERROR, ret,
390 "Adding service to group failed: %s",
391 avahi_strerror(ret));
392 }
393 goto out;
394 }
395
396 service = g_new0(GaEntryGroupServicePrivate, 1);
397 service->public.interface = interface;
398 service->public.protocol = protocol;
399 service->public.flags = flags;
400 service->public.name = g_strdup(name);
401 service->public.type = g_strdup(type);
402 service->public.domain = g_strdup(domain);
403 service->public.host = g_strdup(host);
404 service->public.port = port;
405 service->group = group;
406 service->frozen = FALSE;
407 service->entries = _string_list_to_hash(txt);
408 g_hash_table_insert(priv->services, group, service);
409 out:
410 return (GaEntryGroupService *) service;
411 }
412
ga_entry_group_add_service(GaEntryGroup * group,const gchar * name,const gchar * type,guint16 port,GError ** error,...)413 GaEntryGroupService *ga_entry_group_add_service(GaEntryGroup * group,
414 const gchar * name,
415 const gchar * type,
416 guint16 port,
417 GError ** error, ...) {
418 GaEntryGroupService *ret;
419 AvahiStringList *txt = NULL;
420 va_list va;
421 va_start(va, error);
422 txt = avahi_string_list_new_va(va);
423
424 ret = ga_entry_group_add_service_full_strlist(group,
425 AVAHI_IF_UNSPEC,
426 AVAHI_PROTO_UNSPEC,
427 0,
428 name, type,
429 NULL, NULL,
430 port, error, txt);
431 avahi_string_list_free(txt);
432 va_end(va);
433 return ret;
434 }
435
ga_entry_group_add_service_full(GaEntryGroup * group,AvahiIfIndex interface,AvahiProtocol protocol,AvahiPublishFlags flags,const gchar * name,const gchar * type,const gchar * domain,const gchar * host,guint16 port,GError ** error,...)436 GaEntryGroupService *ga_entry_group_add_service_full(GaEntryGroup * group,
437 AvahiIfIndex interface,
438 AvahiProtocol protocol,
439 AvahiPublishFlags flags,
440 const gchar * name,
441 const gchar * type,
442 const gchar * domain,
443 const gchar * host,
444 guint16 port,
445 GError ** error, ...) {
446 GaEntryGroupService *ret;
447 AvahiStringList *txt = NULL;
448 va_list va;
449
450 va_start(va, error);
451 txt = avahi_string_list_new_va(va);
452
453 ret = ga_entry_group_add_service_full_strlist(group,
454 interface, protocol,
455 flags,
456 name, type,
457 domain, host,
458 port, error, txt);
459 avahi_string_list_free(txt);
460 va_end(va);
461 return ret;
462 }
463
ga_entry_group_add_record(GaEntryGroup * group,AvahiPublishFlags flags,const gchar * name,guint16 type,guint32 ttl,const void * rdata,gsize size,GError ** error)464 gboolean ga_entry_group_add_record(GaEntryGroup * group,
465 AvahiPublishFlags flags,
466 const gchar * name,
467 guint16 type,
468 guint32 ttl,
469 const void *rdata, gsize size, GError ** error) {
470 return ga_entry_group_add_record_full(group,
471 AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
472 flags, name, AVAHI_DNS_CLASS_IN,
473 type, ttl, rdata, size, error);
474 }
475
ga_entry_group_add_record_full(GaEntryGroup * group,AvahiIfIndex interface,AvahiProtocol protocol,AvahiPublishFlags flags,const gchar * name,guint16 clazz,guint16 type,guint32 ttl,const void * rdata,gsize size,GError ** error)476 gboolean ga_entry_group_add_record_full(GaEntryGroup * group,
477 AvahiIfIndex interface,
478 AvahiProtocol protocol,
479 AvahiPublishFlags flags,
480 const gchar * name,
481 guint16 clazz,
482 guint16 type,
483 guint32 ttl,
484 const void *rdata,
485 gsize size, GError ** error) {
486 int ret;
487 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
488 g_assert(group != NULL && priv->group != NULL);
489
490 ret = avahi_entry_group_add_record(priv->group, interface, protocol,
491 flags, name, clazz, type, ttl, rdata,
492 size);
493 if (ret) {
494 if (error != NULL) {
495 *error = g_error_new(GA_ERROR, ret,
496 "Setting raw record failed: %s",
497 avahi_strerror(ret));
498 }
499 return FALSE;
500 }
501 return TRUE;
502 }
503
504
ga_entry_group_service_freeze(GaEntryGroupService * service)505 void ga_entry_group_service_freeze(GaEntryGroupService * service) {
506 GaEntryGroupServicePrivate *p = (GaEntryGroupServicePrivate *) service;
507 p->frozen = TRUE;
508 }
509
ga_entry_group_service_thaw(GaEntryGroupService * service,GError ** error)510 gboolean ga_entry_group_service_thaw(GaEntryGroupService * service, GError ** error) {
511 GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
512 int ret;
513 gboolean result = TRUE;
514
515 AvahiStringList *txt = _hash_to_string_list(priv->entries);
516 ret = avahi_entry_group_update_service_txt_strlst
517 (GA_ENTRY_GROUP_GET_PRIVATE(priv->group)->group,
518 service->interface, service->protocol, service->flags,
519 service->name, service->type, service->domain, txt);
520 if (ret) {
521 if (error != NULL) {
522 *error = g_error_new(GA_ERROR, ret,
523 "Updating txt record failed: %s",
524 avahi_strerror(ret));
525 }
526 result = FALSE;
527 }
528
529 avahi_string_list_free(txt);
530 priv->frozen = FALSE;
531 return result;
532 }
533
ga_entry_group_service_set(GaEntryGroupService * service,const gchar * key,const gchar * value,GError ** error)534 gboolean ga_entry_group_service_set(GaEntryGroupService * service,
535 const gchar * key, const gchar * value,
536 GError ** error) {
537 return ga_entry_group_service_set_arbitrary(service, key,
538 (const guint8 *) value,
539 strlen(value), error);
540
541 }
542
ga_entry_group_service_set_arbitrary(GaEntryGroupService * service,const gchar * key,const guint8 * value,gsize size,GError ** error)543 gboolean ga_entry_group_service_set_arbitrary(GaEntryGroupService * service,
544 const gchar * key, const guint8 * value,
545 gsize size, GError ** error) {
546 GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
547
548 _set_entry(priv->entries, (const guint8 *) key, strlen(key), value, size);
549
550 if (!priv->frozen)
551 return ga_entry_group_service_thaw(service, error);
552 else
553 return TRUE;
554 }
555
ga_entry_group_service_remove_key(GaEntryGroupService * service,const gchar * key,GError ** error)556 gboolean ga_entry_group_service_remove_key(GaEntryGroupService * service,
557 const gchar * key, GError ** error) {
558 GaEntryGroupServicePrivate *priv = (GaEntryGroupServicePrivate *) service;
559 GaEntryGroupServiceEntry entry;
560
561 entry.value = (void*) key;
562 entry.size = strlen(key);
563
564 g_hash_table_remove(priv->entries, &entry);
565
566 if (!priv->frozen)
567 return ga_entry_group_service_thaw(service, error);
568 else
569 return TRUE;
570 }
571
572
ga_entry_group_attach(GaEntryGroup * group,GaClient * client,GError ** error)573 gboolean ga_entry_group_attach(GaEntryGroup * group,
574 GaClient * client, GError ** error) {
575 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
576
577 g_return_val_if_fail(client->avahi_client, FALSE);
578 g_assert(priv->client == NULL || priv->client == client);
579 g_assert(priv->group == NULL);
580
581 priv->client = client;
582 g_object_ref(client);
583
584 priv->group = avahi_entry_group_new(client->avahi_client,
585 _avahi_entry_group_cb, group);
586 if (priv->group == NULL) {
587 if (error != NULL) {
588 int aerrno = avahi_client_errno(client->avahi_client);
589 *error = g_error_new(GA_ERROR, aerrno,
590 "Attaching group failed: %s",
591 avahi_strerror(aerrno));
592 }
593 return FALSE;
594 }
595 return TRUE;
596 }
597
ga_entry_group_commit(GaEntryGroup * group,GError ** error)598 gboolean ga_entry_group_commit(GaEntryGroup * group, GError ** error) {
599 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
600 int ret;
601 ret = avahi_entry_group_commit(priv->group);
602 if (ret) {
603 if (error != NULL) {
604 *error = g_error_new(GA_ERROR, ret,
605 "Committing group failed: %s",
606 avahi_strerror(ret));
607 }
608 return FALSE;
609 }
610 return TRUE;
611 }
612
ga_entry_group_reset(GaEntryGroup * group,GError ** error)613 gboolean ga_entry_group_reset(GaEntryGroup * group, GError ** error) {
614 GaEntryGroupPrivate *priv = GA_ENTRY_GROUP_GET_PRIVATE(group);
615 int ret;
616 ret = avahi_entry_group_reset(priv->group);
617 if (ret) {
618 if (error != NULL) {
619 *error = g_error_new(GA_ERROR, ret,
620 "Resetting group failed: %s",
621 avahi_strerror(ret));
622 }
623 return FALSE;
624 }
625 return TRUE;
626 }
627