1/***
2  This file is part of avahi.
3
4  avahi is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Lesser General Public License as
6  published by the Free Software Foundation; either version 2.1 of the
7  License, or (at your option) any later version.
8
9  avahi is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General
12  Public License for more details.
13
14  You should have received a copy of the GNU Lesser General Public
15  License along with avahi; if not, write to the Free Software
16  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
17  USA.
18***/
19
20
21using System;
22using System.Threading;
23using System.Collections;
24using System.Runtime.InteropServices;
25using Mono.Unix;
26using Mono.Unix.Native;
27
28using Stdlib = Mono.Unix.Native.Stdlib;
29
30namespace Avahi
31{
32    internal enum ResolverEvent {
33        Found,
34        Failure
35    }
36
37    internal enum BrowserEvent {
38        Added,
39        Removed,
40        CacheExhausted,
41        AllForNow,
42        Failure
43    }
44
45    internal delegate int PollCallback (IntPtr ufds, uint nfds, int timeout);
46    internal delegate void ClientCallback (IntPtr client, ClientState state, IntPtr userData);
47
48    public delegate void ClientStateHandler (object o, ClientStateArgs state);
49
50    public class ClientStateArgs : EventArgs
51    {
52        private ClientState state;
53        private ErrorCode error;
54
55        public ClientState State
56        {
57            get { return state; }
58        }
59
60        public ErrorCode Error
61        {
62            get { return error; }
63        }
64
65        public ClientStateArgs (ClientState state, ErrorCode error)
66        {
67            this.state = state;
68            this.error = error;
69        }
70    }
71
72    public enum Protocol {
73        Unspecified = -1,
74        IPv4 = 0,
75        IPv6 = 1
76    }
77
78    internal enum ServerState {
79        Invalid,
80        Registering,
81        Running,
82        Collision
83    }
84
85    public enum ClientState {
86        Registering = ServerState.Registering,
87        Running = ServerState.Running,
88        Collision = ServerState.Collision,
89        Failure = 100,
90        Connecting = 101
91    }
92
93    [Flags]
94    public enum LookupFlags {
95        None = 0,
96        UseWideArea = 1,
97        UseMulticast = 2,
98	NoTxt = 4,
99        NoAddress = 8
100    }
101
102    [Flags]
103    public enum LookupResultFlags {
104        None = 0,
105        Cached = 1,
106        WideArea = 2,
107        Multicast = 4,
108        Local = 8,
109        OurOwn = 16,
110    }
111
112    [Flags]
113    public enum ClientFlags {
114        None = 0,
115        IgnoreUserConfig = 1,
116        NoFail = 2
117    }
118
119    public class Client : IDisposable
120    {
121        private IntPtr handle;
122        private ClientCallback cb;
123        private PollCallback pollcb;
124        private IntPtr spoll;
125
126        private Thread thread;
127
128        [DllImport ("avahi-client")]
129        private static extern IntPtr avahi_client_new (IntPtr poll, ClientFlags flags, ClientCallback handler,
130                                                       IntPtr userData, out int error);
131
132        [DllImport ("avahi-client")]
133        private static extern void avahi_client_free (IntPtr handle);
134
135        [DllImport ("avahi-client")]
136        private static extern IntPtr avahi_client_get_version_string (IntPtr handle);
137
138        [DllImport ("avahi-client")]
139        private static extern IntPtr avahi_client_get_host_name (IntPtr handle);
140
141        [DllImport ("avahi-client")]
142        private static extern IntPtr avahi_client_get_domain_name (IntPtr handle);
143
144        [DllImport ("avahi-client")]
145        private static extern IntPtr avahi_client_get_host_name_fqdn (IntPtr handle);
146
147        [DllImport ("avahi-client")]
148        private static extern ClientState avahi_client_get_state (IntPtr handle);
149
150        [DllImport ("avahi-client")]
151        private static extern int avahi_client_errno (IntPtr handle);
152
153        [DllImport ("avahi-common")]
154        private static extern IntPtr avahi_simple_poll_new ();
155
156        [DllImport ("avahi-common")]
157        private static extern IntPtr avahi_simple_poll_get (IntPtr spoll);
158
159        [DllImport ("avahi-common")]
160        private static extern void avahi_simple_poll_free (IntPtr spoll);
161
162        [DllImport ("avahi-common")]
163        private static extern int avahi_simple_poll_loop (IntPtr spoll);
164
165        [DllImport ("avahi-common")]
166        private static extern void avahi_simple_poll_set_func (IntPtr spoll, PollCallback cb);
167
168        [DllImport ("avahi-common")]
169        private static extern void avahi_simple_poll_quit (IntPtr spoll);
170
171        [DllImport ("avahi-client")]
172        private static extern uint avahi_client_get_local_service_cookie (IntPtr client);
173
174        [DllImport ("avahi-common")]
175        private static extern int avahi_service_name_join (IntPtr buf, int len, byte[] name, byte[] type,
176                                                           byte[] domain);
177
178        [DllImport ("avahi-common")]
179        private static extern int avahi_service_name_split (byte[] service, IntPtr name, int name_len,
180                                                            IntPtr type, int type_len,
181                                                            IntPtr domain, int domain_len);
182
183
184        [DllImport ("libc")]
185        private static extern int poll(IntPtr ufds, uint nfds, int timeout);
186
187        public event ClientStateHandler StateChanged;
188
189        internal IntPtr Handle
190        {
191            get { return handle; }
192        }
193
194        public string Version
195        {
196            get {
197                lock (this) {
198                    return Utility.PtrToString (avahi_client_get_version_string (handle));
199                }
200            }
201        }
202
203        public string HostName
204        {
205            get {
206                lock (this) {
207                    return Utility.PtrToString (avahi_client_get_host_name (handle));
208                }
209            }
210        }
211
212        public string DomainName
213        {
214            get {
215                lock (this) {
216                    return Utility.PtrToString (avahi_client_get_domain_name (handle));
217                }
218            }
219        }
220
221        public string HostNameFqdn
222        {
223            get {
224                lock (this) {
225                    return Utility.PtrToString (avahi_client_get_host_name_fqdn (handle));
226                }
227            }
228        }
229
230        public ClientState State
231        {
232            get {
233                lock (this) {
234                    return (ClientState) avahi_client_get_state (handle);
235                }
236            }
237        }
238
239        public uint LocalServiceCookie
240        {
241            get {
242                lock (this) {
243                    return avahi_client_get_local_service_cookie (handle);
244                }
245            }
246        }
247
248        internal ErrorCode LastError
249        {
250            get {
251                lock (this) {
252                    return (ErrorCode) avahi_client_errno (handle);
253                }
254            }
255        }
256
257        public Client (ClientFlags flags)
258        {
259            spoll = avahi_simple_poll_new ();
260
261            pollcb = OnPollCallback;
262            avahi_simple_poll_set_func (spoll, pollcb);
263            IntPtr poll = avahi_simple_poll_get (spoll);
264            cb = OnClientCallback;
265
266            int error;
267            handle = avahi_client_new (poll, flags, cb, IntPtr.Zero, out error);
268            if (error != 0)
269                throw new ClientException (error);
270
271            thread = new Thread (PollLoop);
272            thread.IsBackground = true;
273            thread.Start ();
274        }
275
276        public Client () : this (ClientFlags.None) {
277        }
278
279        ~Client ()
280        {
281            Dispose ();
282        }
283
284        public void Dispose ()
285        {
286            if (handle != IntPtr.Zero) {
287                lock (this) {
288                    avahi_client_free (handle);
289                    handle = IntPtr.Zero;
290
291                    avahi_simple_poll_quit (spoll);
292                    Monitor.Wait (this);
293
294                    avahi_simple_poll_free (spoll);
295                }
296            }
297        }
298
299        public static string JoinServiceName (string name, string type, string domain)
300        {
301            int len = 4 * (name.Length + type.Length + domain.Length) + 4;
302            IntPtr buf = Stdlib.malloc ((ulong) len);
303
304            int ret = avahi_service_name_join (buf, len,
305                                               Utility.StringToBytes (name),
306                                               Utility.StringToBytes (type),
307                                               Utility.StringToBytes (domain));
308
309            if (ret < 0) {
310                Utility.Free (buf);
311                return null; // FIXME, should throw exception
312            }
313
314            string service = Utility.PtrToString (buf);
315            Utility.Free (buf);
316
317            return service;
318        }
319
320        public static void SplitServiceName (string service, out string name, out string type, out string domain)
321        {
322            int len = 1024;
323
324            IntPtr namePtr = Stdlib.malloc ((ulong) len);
325            IntPtr typePtr = Stdlib.malloc ((ulong) len);
326            IntPtr domainPtr = Stdlib.malloc ((ulong) len);
327
328            int ret = avahi_service_name_split (Utility.StringToBytes (service), namePtr, len, typePtr, len,
329                                                domainPtr, len);
330
331            if (ret < 0) {
332                Utility.Free (namePtr);
333                Utility.Free (typePtr);
334                Utility.Free (domainPtr);
335
336                name = null;
337                type = null;
338                domain = null;
339                return;
340            }
341
342            name = Utility.PtrToString (namePtr);
343            type = Utility.PtrToString (typePtr);
344            domain = Utility.PtrToString (domainPtr);
345
346            Utility.Free (namePtr);
347            Utility.Free (typePtr);
348            Utility.Free (domainPtr);
349        }
350
351        internal void ThrowError ()
352        {
353            ErrorCode error = LastError;
354
355            if (error != ErrorCode.Ok)
356                throw new ClientException (error);
357        }
358
359        private void OnClientCallback (IntPtr client, ClientState state, IntPtr userData)
360        {
361            if (StateChanged != null)
362                StateChanged (this, new ClientStateArgs (state, LastError));
363        }
364
365        private int OnPollCallback (IntPtr ufds, uint nfds, int timeout) {
366            Monitor.Exit (this);
367            int result = poll (ufds, nfds, timeout);
368            Monitor.Enter (this);
369            return result;
370        }
371
372        private void PollLoop () {
373            try {
374                lock (this) {
375                    avahi_simple_poll_loop (spoll);
376                    Monitor.Pulse (this);
377                }
378            } catch (Exception e) {
379                Console.Error.WriteLine ("Error in avahi-sharp event loop: " + e);
380            }
381        }
382    }
383}
384