chiark / gitweb /
Update .gitignore for recent debhelper droppings.
[chiark-tcl.git] / maskmap / maskmap.c
1 /*
2  * maskmap - Tcl extension for address mask map data structures
3  * Copyright 2006-2012 Ian Jackson
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this library; if not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include "chiark_tcl_hbytes.h"
20
21 /*---------- operations on AddrMap_Entry ----------*/
22
23 static void ame_init(AddrMap_Entry *ame) {
24   ame->prefixlen= -1;
25   ame->prefix= 0;
26   ame->data= 0;
27 }
28
29 static unsigned ame_clear_unwanted(AddrMap_Entry *ame, int bytes) {
30   /* returns non-0 iff some bits were cleared */
31   int sparebits;
32   unsigned result, sparemask;
33   Byte *datap;
34
35   sparebits= bytes * 8 - ame->prefixlen;
36   if (!sparebits) return 0;
37
38   sparemask= (1u << sparebits) - 1;
39   datap= &ame->prefix[bytes-1];
40
41   result= *datap & sparemask;
42   *datap &= ~sparemask;
43
44   return result;
45 }
46
47 static int ame_parsekey(Tcl_Interp *ip, AddrMap_Entry *ame,
48                         Tcl_Obj *prefixo, Tcl_Obj *prefixbitso,
49                         int inmap) {
50  /* *ame should be blank entry; after exit (even error exit) it will be valid
51   * - on errors, it will be blank.  inmap is 1 if we're parsing an existing
52   * map or 0 if it's an entry to be added or modified. */
53   HBytes_Value prefix;
54   int suppliedprefixbytes, prefixbits, wantprefixbytes;
55   const Byte *data;
56   int rc;
57
58   hbytes_empty(&prefix);
59   
60   rc= pat_hb(ip,prefixo,&prefix);  if (rc) goto x_rc;
61   rc= pat_int(ip,prefixbitso,&prefixbits);  if (rc) goto x_rc;
62
63   wantprefixbytes= prefix_bytes(prefixbits);
64   suppliedprefixbytes= hbytes_len(&prefix);
65
66   if (suppliedprefixbytes < wantprefixbytes) {
67     rc= staticerr(ip, "addr-map entry PREFIX too short for PREFIX-LEN",
68                   "HBYTES ADDRMAP SYNTAX UNDERRUN");
69     goto x_rc;
70   }
71   if (inmap && suppliedprefixbytes > wantprefixbytes) {
72     rc= staticerr(ip, "addr-map existing entry PREFIX too long for PREFIX-LEN",
73                   "HBYTES ADDRMAP SYNTAX OVERRUN");
74     goto x_rc;
75   }
76
77   ame->prefixlen= prefixbits;
78   ame->prefix= TALLOC(wantprefixbytes);  assert(ame->prefix);
79   memcpy(ame->prefix, data, wantprefixbytes);
80
81   if (ame_clear_unwanted(ame, wantprefixbytes)) {
82     rc= staticerr(ip, "addr-map entry PREFIX contains bits excluded"
83                   " by PREFIX-LEN", "HBYTES ADDRMAP SYNTAX EXCLBITS");
84     goto x_rc;
85   }
86
87   return TCL_OK;
88
89  x_rc:
90   ame_free(ame);
91   return rc;
92 }
93
94 static int ame_contains(const AddrMap_Entry *ref, const Byte *addr, int len) {
95   int directbytes, leftoverbits;
96   
97   assert(len >= ref->prefixlen);
98   
99   directbytes= ref->prefixlen / 8;
100   if (memcmp(ref->prefix, addr, directbytes)) return 0;
101
102   leftoverbits= ref->prefixlen % 8;
103   if (leftoverbits)
104     if ((addr[directbytes] & (0xffu << leftoverbits))
105         != search->prefix[directbytes])
106       return 0;
107
108   return 1;
109 }  
110
111 static int ame_compare(const AddrMap_Entry *a, const AddrMap_Entry *b) {
112   /*    +2 =    a   covers later range of address space than  b
113    *    +1 =    a   wholly contains but is not equal to       b
114    *     0 =    a   is identical to                           b
115    *    -1 =    b   wholly contains but is not equal to       a
116    *    -2 =    b   covers later range of address space than  a
117    */
118   int al= a->prefixlen;
119   int bl= b->prefixlen;
120   int ml, d;
121
122   if (al==bl) { ml=al; }
123   else if (al<bl) { ml=al; if (ame_contains(a,b->prefix,bl)) return +1; }
124   else if (bl<al) { ml=bl; if (ame_contains(b,a->prefix,al)) return -1; }
125
126   d= memcmp(b->prefix, a->prefix, prefix_bytes(ml));
127   return (d > 0 ? +2 :
128           d < 0 ? -2 :
129           0);
130 }
131
132 /*---------- searching maps ----------*/
133
134 typedef enum {
135   sr_notfound,
136   sr_exact,
137   sr_inbig,
138   sr_aroundsmall
139 } Search_Result;
140
141 static int
142 am_binarychop(AddrMap_Value *am, int low_oreq, int high_strict, void *u,
143               int (*test)(AddrMap_Entry *am, void *u) /* -ve => look left */,
144               int *found_r) {
145   int mid, cmp;
146   
147   for (;;) {
148     if (high_strict <= low_oreq) {
149       assert(high_strict == low_oreq);
150       *found_r= 0;
151       return high_strict;
152     }
153
154     mid= (high_strict + low_oreq) / 2;
155     cmp= test(&am->entries[mid], u);
156
157     if (!cmp) {
158       *found_r= 1;
159       return mid;
160     }
161
162     if (cmp < 0)
163       high_strict= mid;
164     else
165       low_oreq= mid+1;
166   }
167 }
168
169 struct am_search_u {
170   int forbid_aroundsmall;
171   AddrMap_Entry proposed;
172   Search_Result sr;
173 };
174
175 static int
176 am_search_binchoptest(AddrMap_Entry *ame, void *u_v) {
177   struct am_search_u *u= u_v;
178   int cmp;
179
180   cmp= ame_compare(&u.proposed, ame);
181   switch (cmp) {
182   case -1:  u->sr= sr_inbig;        return  0;
183   case  0:  u->sr= sr_exact;        return  0;
184   case +1:  u->sr= sr_aroundsmall;  return  0;
185   default:                          return cmp;
186   }
187 }
188
189 static Search_Result
190 am_search(AddrMap_Value *am, const AddrMap_Entry *proposed, int *place_r) {
191   int place, found;
192   struct am_search_u u;
193
194   u.forbid_aroundsmall= forbid_aroundsmall;
195   u.proposed= proposed;
196   u.sr= sr_notfound;
197
198   *place_r= am_binarychop(am, 0, am.used, &u, am_search_binchoptest, &found);
199
200   assert(!!found == (u.sr != sr_notfound));
201   return u.sr;
202 }
203
204 /*---------- useful operations on AddrMap_Value etc. ----------*/
205
206 /*---------- amendment (complex algorithm) ----------*/
207
208 struct am_amend_aroundsmall_u {
209   AddrMap_Entry *new;
210   int sign;
211 };
212
213
214 static int
215 am_amend_aroundsmall_binchoptest(AddrMap_Entry *search, void *u_v) {
216   struct am_amend_aroundsmall_u *u= u_v;
217
218   cmp= u->sign * ame_compare(search, u->new);
219
220   switch (cmp) {
221   case +2:  return -u->sign;
222   case +1:  return +u->sign;
223   default:  abort();
224   }
225 }
226
227 int do_addrmap_amend(ClientData cd, Tcl_Interp *ip,
228                      AddrMap_Var map, Tcl_Obj *prefix,
229                      Tcl_Obj *preflen, Tcl_Obj *data) {
230   AddrMap_Value *am= map.am;
231   AddrMap_Entry new, *fragment;
232   AddrMap_Entry *breaking, *replacements;
233   int rc, insertat, findend, cmp, nreplacements, new_used;
234   struct am_amend_aroundsmall_u u;
235
236   ame_init(&new);
237   
238   rc= ame_parsekey(ip,&new,prefix,preflen,0);  if (rc) return rc;
239
240   sr= am_search(am, &new, &searched);
241
242   replacements= &new;
243   nreplacements= 1;
244   replace_start= searched;
245   replace_end= searched;
246
247   switch (sr) {
248
249   case sr_notfound:
250     break;
251
252   case sr_exact:
253     replace_end= searched+1;
254     break;
255
256   case sr_aroundsmall:
257     u.ame= new;
258     u.sign= -1;
259     replace_start= am_binarychop(am, 0, searched, &u,
260                                  am_amend_aroundsmall_binchoptest, &dummy);
261     u.sign= +1;
262     replace_end= am_binarychop(am, searched+1, am.used, &u,
263                                  am_amend_aroundsmall_binchoptest, &dummy);
264     break;
265
266   case sr_inbig:
267     /* Urgh, we need to break it up.  This produces
268      *   - innermost prefix (the new one) as specified
269      *   - one for each bitlength
270      *       <= innermost
271      *        > outermost (the existing one)
272      *     each one specifying the outermost prefix plus zero, one,
273      *     two, etc. bits of the innermost followed by one bit
274      *     opposite to the innermost, with the outermost's data
275      * Eg, if we have ff/8=>A and want to amend so that ffff/16=>B
276      * then we replace ff/8 with ff0/9=>A ff8/10=>A ffc/11=>A ...
277      * ... fff8/14=>A fffc/15=>A fffe/16=>A ffff/16=>B.
278      */
279
280     breaking= &am.entries[searched];
281     nreplacements= new.prefix - breaking->prefixlen + 1;
282     fixme check integer overflow ^
283     replacements= TALLOC(sizeof(*replacements) * nreplacements);
284
285     for (fragmentlen= breaking->prefixlen + 1,
286            left_insert= 0, right_insert= nreplacements;
287          fragmentlen <= new.prefix;
288          fragmentlen++) {
289       int fragmentbytes;
290
291       fragmentbytes= prefix_bytes(fragmentlen)
292       fragment->prefixlen= fragmentlen;
293       fragment->prefix= TALLOC(fragmentbytes);
294       memcpy(fragment->prefix, new.prefix, fragmentbytes);
295       ame_clear_unwanted(fragment, fragmentbytes);
296
297       fragment->prefix[fragmentbytes] ^=
298         0x80u >> ((fragmentlen+7) & 7);
299
300       switch (ame_compare(&fragment, &new)) {
301       case -2:  replacements[left_insert++]=  fragment;  break;
302       case +2:  replacements[--right_insert]= fragment;  break;
303       default: abort();
304       }
305     }
306     assert(left_insert == right_insert-1);
307     replacements[left_insert]= new;
308     ame_init(&new);
309
310     replace_end= searched+1;
311     break;
312
313   }
314
315   new_used= am.used - (replace_end - replace_start) + nreplacements;
316
317   if (new_used > am.space)
318     am_reallocentries(am, new_used * 2);
319
320   for (scan=replacements, i=0;
321        i < nreplacements;
322        scan++, i++) {
323     scan->data= data;
324     Tcl_IncrRefCount(scan->data);
325   }
326
327   for (i= replace_start, scan= am.entries+i;
328        i < replace_end;
329        i++, scan++) {
330     ame_free(scan);
331   }
332
333   memmove(am.entries + replace_start + nreplacements,
334           am.entries + replace_end,
335           sizeof(*am.entries) * (am.used - replace_end));
336
337   memcpy(am.entries + replace_start,
338          replacements,
339          sizeof(*am.entries) * nreplacements);
340
341   am.used= new_used;
342   if (replacements != &new)
343     /* we don't bother freeing the actual array elements because
344      * if replacements!=&new the array is only full if we're
345      * committed and have already copied the values into the actual
346      * AddrMap_Value. */
347     TFREE(replacements);
348
349   return TCL_OK;
350 }
351
352 /*---------- other substantial operations on mask maps ----------*/
353
354 int do_addrmap_lookup(ClientData cd, Tcl_Interp *ip,
355                       Tcl_Obj *mapo, HBytes_Value addrhb, Tcl_Obj *def,
356                       Tcl_Obj **result) {
357   AddrMap_Value *am= (void*)&mapo->internalRep;
358   const Byte *addr= hbytes_data(&addrhb);
359   int addrbytes= hbytes_len(&addrhb);
360   int i, addrbits, place;
361   Search_Result sr;
362
363   addrbits= addrbytes * 8;
364   sr= am_search(am, addr, addrbits, &place);
365
366   switch (sr) {
367
368   case sr_notfound:
369     if (!def) return staticerr(ip, "address not found in addr-map",
370                                "HBYTES ADDRMAP NOMATCH");
371     *result= def;
372     break;
373
374   case sr_aroundsmall:
375     return staticerr(ip, "address shorter than mask in map",
376                      "HBYTES ADDRMAP UNDERRUN");
377
378   case sr_exact:
379   case sr_inbig:
380     *result= am.entres[place].data;
381     break;
382     
383   }
384
385   return TCL_OK;
386 }
387
388 /*---------- Tcl type and arg parsing functions ----------*/
389