Home | History | Annotate | Line # | Download | only in ServiceRegistration
omr-publisher.c revision 1.1
      1 /* omr-publisher.c
      2  *
      3  * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *     https://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  *
     17  * This file contains an implementation of the Off-Mesh-Routable (OMR)
     18  * prefix publisher state machine.
     19  */
     20 
     21 #include <stdlib.h>
     22 #include <string.h>
     23 #include <stdio.h>
     24 #include <unistd.h>
     25 #include <pwd.h>
     26 #include <errno.h>
     27 #include <sys/socket.h>
     28 #include <netinet/in.h>
     29 #include <arpa/inet.h>
     30 #include <fcntl.h>
     31 #include <time.h>
     32 #include <dns_sd.h>
     33 #include <net/if.h>
     34 #include <inttypes.h>
     35 #include <sys/resource.h>
     36 #include "srp.h"
     37 #include "dns-msg.h"
     38 #include "srp-crypto.h"
     39 #include "ioloop.h"
     40 #include "srp-gw.h"
     41 #include "srp-proxy.h"
     42 #include "srp-mdns-proxy.h"
     43 #include "config-parse.h"
     44 #include "cti-services.h"
     45 #include "route.h"
     46 #include "dnssd-proxy.h"
     47 
     48 
     49 #define STATE_MACHINE_IMPLEMENTATION 1
     50 typedef enum {
     51     omr_publisher_state_invalid,
     52     omr_publisher_state_startup,
     53     omr_publisher_state_check_for_dhcp,
     54     omr_publisher_state_not_publishing,
     55     omr_publisher_state_publishing_dhcp,
     56     omr_publisher_state_publishing_ula,
     57 } state_machine_state_t;
     58 #define state_machine_state_invalid omr_publisher_state_invalid
     59 
     60 #include "state-machine.h"
     61 #include "service-publisher.h"
     62 #include "thread-service.h"
     63 #include "omr-watcher.h"
     64 #include "omr-publisher.h"
     65 #include "route-tracker.h"
     66 
     67 typedef struct omr_publisher {
     68     int ref_count;
     69     state_machine_header_t state_header;
     70 
     71     route_state_t *route_state;
     72     wakeup_t *NULLABLE wakeup_timer;
     73     void *dhcp_client;
     74     omr_watcher_callback_t *omr_watcher_callback;
     75     omr_watcher_t *omr_watcher;
     76     void (*reconnect_callback)(void *context);
     77 
     78     omr_prefix_t *published_prefix;
     79     omr_prefix_t *publication_queue;
     80     interface_t *dhcp_interface;
     81     struct in6_addr ula_prefix;
     82     struct in6_addr dhcp_prefix;
     83     struct in6_addr new_dhcp_prefix;
     84     int dhcp_prefix_length, new_dhcp_prefix_length;
     85     uint32_t dhcp_preferred_lifetime, new_dhcp_preferred_lifetime;
     86     int min_start;
     87     omr_prefix_priority_t omr_priority, force_priority;
     88     bool ula_prefix_published, dhcp_prefix_published;
     89     bool dhcp_wanted;
     90     bool dhcp_blocked;
     91     bool first_time;
     92     bool force_publication;
     93 } omr_publisher_t;
     94 
     95                                                                                                                        \
     96 #define STATE_MACHINE_HEADER_TO_PUBLISHER(state_header)                                                                \
     97     if (state_header->state_machine_type != state_machine_type_omr_publisher) {                                        \
     98         ERROR("state header type isn't omr_publisher: %d", state_header->state_machine_type);                          \
     99         return omr_publisher_state_invalid;                                                                            \
    100     }                                                                                                                  \
    101     omr_publisher_t *publisher = state_header->state_object
    102 
    103 static state_machine_state_t omr_publisher_action_startup(state_machine_header_t *state_header,
    104                                                           state_machine_event_t *event);
    105 static state_machine_state_t omr_publisher_action_check_for_dhcp(state_machine_header_t *state_header,
    106                                                                  state_machine_event_t *event);
    107 static state_machine_state_t omr_publisher_action_not_publishing(state_machine_header_t *state_header,
    108                                                                  state_machine_event_t *event);
    109 static state_machine_state_t omr_publisher_action_publishing_dhcp(state_machine_header_t *state_header,
    110                                                                   state_machine_event_t *event);
    111 static state_machine_state_t omr_publisher_action_publishing_ula(state_machine_header_t *state_header,
    112                                                                  state_machine_event_t *event);
    113 
    114 #define OMR_PUB_NAME_DECL(name) omr_publisher_state_##name, #name
    115 static state_machine_decl_t omr_publisher_states[] = {
    116     { OMR_PUB_NAME_DECL(invalid),                            NULL },
    117     { OMR_PUB_NAME_DECL(startup),                            omr_publisher_action_startup },
    118     { OMR_PUB_NAME_DECL(check_for_dhcp),                     omr_publisher_action_check_for_dhcp },
    119     { OMR_PUB_NAME_DECL(not_publishing),                     omr_publisher_action_not_publishing },
    120     { OMR_PUB_NAME_DECL(publishing_dhcp),                    omr_publisher_action_publishing_dhcp },
    121     { OMR_PUB_NAME_DECL(publishing_ula),                     omr_publisher_action_publishing_ula },
    122 };
    123 #define OMR_PUBLISHER_NUM_CONNECTION_STATES ((sizeof(omr_publisher_states)) / (sizeof(state_machine_decl_t)))
    124 
    125 static void omr_publisher_discontinue_dhcp(omr_publisher_t *publisher);
    126 static void omr_publisher_queue_prefix_update(omr_publisher_t *publisher, struct in6_addr *prefix_address,
    127                                               omr_prefix_priority_t priority, bool preferred,
    128                                               thread_service_publication_state_t initial_state);
    129 
    130 static void
    131 omr_publisher_finalize(omr_publisher_t *publisher)
    132 {
    133     free(publisher->state_header.name);
    134     free(publisher);
    135 }
    136 RELEASE_RETAIN_FUNCS(omr_publisher);
    137 
    138 static void
    139 omr_publisher_context_release(route_state_t *UNUSED route_state, void *context)
    140 {
    141     omr_publisher_t *publisher = context;
    142     RELEASE_HERE(publisher, omr_publisher);
    143 }
    144 
    145 static void
    146 omr_publisher_event_prefix_update_finished_finalize(state_machine_event_t *event)
    147 {
    148     if (event->thread_prefixes != NULL) {
    149         omr_prefix_release(event->thread_prefixes);
    150         event->thread_prefixes = NULL;
    151     }
    152 }
    153 
    154 static void
    155 omr_publisher_watcher_callback(route_state_t *UNUSED NONNULL route_state, void *NULLABLE context, omr_watcher_event_type_t event_type,
    156                                omr_prefix_t *NULLABLE prefixes, omr_prefix_t *NULLABLE prefix)
    157 {
    158     omr_publisher_t *publisher = context;
    159 
    160     // On startup, notice any prefixes with the user flag set: we didn't publish these, so they must be left over from
    161     // a crash or restart.
    162     if (publisher->first_time && event_type == omr_watcher_event_prefix_added && prefix != NULL && prefix->user) {
    163         // We might actually publish our own prefix before we go through this loop for the first time. If that's the case,
    164         // don't unpublish it!
    165         if (publisher->published_prefix == NULL ||
    166             in6addr_compare(&prefix->prefix, &publisher->published_prefix->prefix))
    167         {
    168             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    169             INFO("removing stale prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    170                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    171             omr_publisher_queue_prefix_update(publisher, &prefix->prefix, omr_prefix_priority_low, false, want_delete);
    172         }
    173     }
    174 
    175     // We don't otherwise care about "prefix appeared" and "prefix disappeared" events, nor about flag change events--
    176     // just about the final state
    177     if (event_type == omr_watcher_event_prefix_update_finished) {
    178         state_machine_event_t *event = state_machine_event_create(state_machine_event_type_prefix,
    179                                                                   omr_publisher_event_prefix_update_finished_finalize);
    180         if (event == NULL) {
    181             ERROR("unable to allocate event to deliver");
    182             return;
    183         }
    184         event->thread_prefixes = prefixes;
    185         if (prefixes != NULL) {
    186             omr_prefix_retain(event->thread_prefixes);
    187         }
    188         state_machine_event_deliver(&publisher->state_header, event);
    189         RELEASE_HERE(event, state_machine_event);
    190 
    191         publisher->first_time = false;
    192     }
    193 }
    194 
    195 void
    196 omr_publisher_set_omr_watcher(omr_publisher_t *publisher, omr_watcher_t *watcher)
    197 {
    198     if (watcher != NULL) {
    199         publisher->omr_watcher = watcher;
    200         omr_watcher_retain(publisher->omr_watcher);
    201         publisher->omr_watcher_callback = omr_watcher_callback_add(publisher->omr_watcher,
    202                                                                    omr_publisher_watcher_callback,
    203                                                                    omr_publisher_context_release, publisher);
    204         RETAIN_HERE(publisher, omr_publisher); // The omr watcher callback holds a reference to the publisher
    205     }
    206 }
    207 
    208 void omr_publisher_set_reconnect_callback(omr_publisher_t *NONNULL publisher,
    209                                           void (*NULLABLE reconnect_callback)(void *NULLABLE context))
    210 {
    211     publisher->reconnect_callback = reconnect_callback;
    212 }
    213 
    214 void
    215 omr_publisher_cancel(omr_publisher_t *publisher)
    216 {
    217     if (publisher->wakeup_timer != NULL) {
    218         ioloop_cancel_wake_event(publisher->wakeup_timer);
    219         ioloop_wakeup_release(publisher->wakeup_timer);
    220         publisher->wakeup_timer = NULL;
    221     }
    222     if (publisher->omr_watcher_callback != NULL) {
    223         omr_watcher_callback_cancel(publisher->omr_watcher, publisher->omr_watcher_callback);
    224         publisher->omr_watcher_callback = NULL;
    225     }
    226     if (publisher->omr_watcher != NULL) {
    227         omr_watcher_release(publisher->omr_watcher);
    228         publisher->omr_watcher = NULL;
    229     }
    230     if (publisher->published_prefix != NULL) {
    231         omr_publisher_unpublish_prefix(publisher);
    232     }
    233     if (publisher->dhcp_client) {
    234         omr_publisher_discontinue_dhcp(publisher);
    235     }
    236     if (publisher->dhcp_interface != NULL) {
    237         interface_release(publisher->dhcp_interface);
    238         publisher->dhcp_interface = NULL;
    239     }
    240     state_machine_cancel(&publisher->state_header);
    241 }
    242 
    243 omr_publisher_t *
    244 omr_publisher_create(route_state_t *route_state, const char *name)
    245 {
    246     omr_publisher_t *ret = NULL, *publisher = calloc(1, sizeof(*publisher));
    247     if (publisher == NULL) {
    248         return publisher;
    249     }
    250     RETAIN_HERE(publisher, omr_publisher);
    251     publisher->wakeup_timer = ioloop_wakeup_create();
    252     if (publisher->wakeup_timer == NULL) {
    253         ERROR("wakeup timer alloc failed");
    254         goto out;
    255     }
    256 
    257     if (!state_machine_header_setup(&publisher->state_header,
    258                                     publisher, name,
    259                                     state_machine_type_omr_publisher,
    260                                     omr_publisher_states,
    261                                     OMR_PUBLISHER_NUM_CONNECTION_STATES)) {
    262         ERROR("header setup failed");
    263         goto out;
    264     }
    265 
    266     publisher->route_state = route_state;
    267 
    268     // Set the first_time flag so that we'll know to remove any locally-published on-mesh prefixes.
    269     publisher->first_time = true;
    270     publisher->min_start = OMR_PUBLISHER_MIN_START;
    271 
    272     ret = publisher;
    273     publisher = NULL;
    274 out:
    275     if (publisher != NULL) {
    276         RELEASE_HERE(publisher, omr_publisher);
    277     }
    278     return ret;
    279 }
    280 
    281 void
    282 omr_publisher_start(omr_publisher_t *publisher)
    283 {
    284     state_machine_next_state(&publisher->state_header, omr_publisher_state_startup);
    285 }
    286 
    287 void
    288 omr_publisher_force_publication(omr_publisher_t *publisher, omr_prefix_priority_t priority)
    289 {
    290     publisher->force_publication = true;
    291     if (publisher->state_header.state == omr_publisher_state_publishing_dhcp ||
    292         publisher->state_header.state == omr_publisher_state_publishing_ula)
    293     {
    294         ERROR("already publishing");
    295         return;
    296     } else {
    297         INFO("forcing publication");
    298     }
    299     publisher->force_publication = true;
    300     publisher->force_priority = priority;
    301     state_machine_next_state(&publisher->state_header, omr_publisher_state_publishing_ula);
    302 }
    303 
    304 omr_prefix_t *NULLABLE
    305 omr_publisher_published_prefix_get(omr_publisher_t *publisher)
    306 {
    307     return publisher->published_prefix;
    308 }
    309 
    310 static void
    311 omr_publisher_wait_expired(void *context)
    312 {
    313     omr_publisher_t *publisher = context;
    314     state_machine_event_t *event = state_machine_event_create(state_machine_event_type_timeout, NULL);
    315     if (event == NULL) {
    316         ERROR("unable to allocate event to deliver");
    317         return;
    318     }
    319     state_machine_event_deliver(&publisher->state_header, event);
    320     RELEASE_HERE(event, state_machine_event);
    321 }
    322 
    323 static void
    324 omr_publisher_wakeup_release(void *context)
    325 {
    326     omr_publisher_t *publisher = context;
    327     RELEASE_HERE(publisher, omr_publisher);
    328 }
    329 
    330 static void
    331 omr_publisher_send_dhcp_event(omr_publisher_t *publisher, struct in6_addr *prefix, int prefix_length, uint32_t preferred_lifetime)
    332 {
    333     state_machine_event_t *event = state_machine_event_create(state_machine_event_type_dhcp, NULL);
    334     if (event == NULL) {
    335         ERROR("unable to allocate event to deliver");
    336         return;
    337     }
    338     if (prefix == NULL) {
    339         SEGMENTED_IPv6_ADDR_GEN_SRP(&publisher->dhcp_prefix.s6_addr, prefix_buf);
    340         INFO("DHCPv6 prefix withdrawn " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d, lifetime = %d",
    341              SEGMENTED_IPv6_ADDR_PARAM_SRP(&publisher->dhcp_prefix.s6_addr, prefix_buf),
    342              publisher->dhcp_prefix_length, preferred_lifetime);
    343         in6addr_zero(&publisher->new_dhcp_prefix);
    344     } else {
    345         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->s6_addr, prefix_buf);
    346         INFO("received DHCPv6 prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d, lifetime = %d",
    347              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->s6_addr, prefix_buf), prefix_length, preferred_lifetime);
    348         publisher->new_dhcp_prefix = *prefix;
    349     }
    350     publisher->new_dhcp_prefix_length = prefix_length;
    351     publisher->new_dhcp_preferred_lifetime = preferred_lifetime;
    352     state_machine_event_deliver(&publisher->state_header, event);
    353     RELEASE_HERE(event, state_machine_event);
    354 }
    355 
    356 
    357 static void
    358 omr_publisher_initiate_dhcp(omr_publisher_t *publisher)
    359 {
    360     if (!publisher->dhcp_blocked) {
    361         publisher->dhcp_wanted = true;
    362     }
    363     publisher->dhcp_client = (void *)-1;
    364 }
    365 
    366 void
    367 omr_publisher_interface_configuration_changed(omr_publisher_t *publisher)
    368 {
    369     // Check to see if DHCP interface became inactive/ineligible
    370     if (publisher->dhcp_interface != NULL) {
    371         if (publisher->dhcp_interface->inactive || publisher->dhcp_interface->ineligible) {
    372             // If we have a DHCPv6 client running, we need to discontinue it.
    373             if (publisher->dhcp_client != NULL) {
    374                 omr_publisher_dhcp_client_deactivate(publisher, (intptr_t)publisher->dhcp_client);
    375             }
    376             interface_release(publisher->dhcp_interface);
    377             publisher->dhcp_interface = NULL;
    378         }
    379     }
    380     if (publisher->dhcp_wanted && publisher->dhcp_client == NULL) {
    381         // Start DHCP on the new interface (if there is one)
    382         omr_publisher_initiate_dhcp(publisher);
    383     }
    384 }
    385 
    386 static void
    387 omr_publisher_discontinue_dhcp(omr_publisher_t *publisher)
    388 {
    389     INFO("discontinuing DHCP PD client");
    390     omr_publisher_dhcp_client_deactivate(publisher, (intptr_t)publisher->dhcp_client);
    391     publisher->dhcp_wanted = false;
    392 }
    393 
    394 static bool
    395 omr_publisher_dhcp_prefix_available(omr_publisher_t *publisher)
    396 {
    397     if (publisher->new_dhcp_preferred_lifetime != 0 && publisher->new_dhcp_prefix_length <= 64) {
    398         return true;
    399     }
    400     return false;
    401 }
    402 
    403 static bool
    404 omr_publisher_dhcp_prefix_changed(omr_publisher_t *publisher)
    405 {
    406     if (publisher->new_dhcp_prefix_length != publisher->dhcp_prefix_length ||
    407         publisher->new_dhcp_preferred_lifetime != publisher->dhcp_preferred_lifetime ||
    408         in6addr_compare(&publisher->new_dhcp_prefix, &publisher->dhcp_prefix))
    409     {
    410         return true;
    411     }
    412     return false;
    413 }
    414 
    415 static bool
    416 omr_publisher_dhcp_prefix_lost(omr_publisher_t *publisher)
    417 {
    418     if (publisher->new_dhcp_prefix_length == 0) {
    419         return true;
    420     }
    421     return false;
    422 }
    423 
    424 static void
    425 omr_publisher_install_new_dhcp_prefix(omr_publisher_t *publisher)
    426 {
    427     publisher->dhcp_prefix = publisher->new_dhcp_prefix;
    428     publisher->dhcp_prefix_length = publisher->new_dhcp_prefix_length;
    429     publisher->dhcp_preferred_lifetime = publisher->new_dhcp_preferred_lifetime;
    430 }
    431 
    432 static void
    433 omr_publisher_prefix_init_from_published(omr_publisher_t *publisher, struct in6_addr *prefix, int *prefix_length)
    434 {
    435     if (publisher->published_prefix != NULL) {
    436         in6addr_copy(prefix, &publisher->published_prefix->prefix);
    437         *prefix_length = publisher->published_prefix->prefix_length;
    438     } else {
    439         in6addr_zero(prefix);
    440         *prefix_length = 64;
    441     }
    442 }
    443 
    444 static bool
    445 omr_publisher_prefix_present(omr_publisher_t *publisher, omr_prefix_priority_t priority)
    446 {
    447     struct in6_addr prefix;
    448     int prefix_length;
    449     if (publisher->omr_watcher == NULL) {
    450         FAULT("expecting an omr_watcher to be on the publisher");
    451         return true; // Saying yes because we have no watcher and hence can't behave correctly.
    452     }
    453     omr_publisher_prefix_init_from_published(publisher, &prefix, &prefix_length);
    454     return omr_watcher_prefix_present(publisher->omr_watcher, priority, &prefix, prefix_length);
    455 }
    456 
    457 static bool
    458 omr_publisher_high_prefix_present(omr_publisher_t *publisher)
    459 {
    460     bool ret = omr_publisher_prefix_present(publisher, omr_prefix_priority_high);
    461     if (ret) {
    462         INFO("setting publisher->omr_priority to high");
    463         publisher->omr_priority = omr_prefix_priority_high;
    464     }
    465     return ret;
    466 }
    467 
    468 static bool
    469 omr_publisher_medium_prefix_present(omr_publisher_t *publisher)
    470 {
    471     bool ret = omr_publisher_prefix_present(publisher, omr_prefix_priority_medium);
    472     if (ret) {
    473         INFO("setting publisher->omr_priority to medium");
    474         publisher->omr_priority = omr_prefix_priority_medium;
    475     }
    476     return ret;
    477 }
    478 
    479 static bool
    480 omr_publisher_medium_or_high_prefix_present(omr_publisher_t *publisher)
    481 {
    482     return omr_publisher_medium_prefix_present(publisher) || omr_publisher_high_prefix_present(publisher);
    483 }
    484 
    485 static bool
    486 omr_publisher_low_prefix_present(omr_publisher_t *publisher)
    487 {
    488     return omr_publisher_prefix_present(publisher, omr_prefix_priority_low);
    489 }
    490 
    491 static bool
    492 omr_publisher_prefix_wins(omr_publisher_t *publisher, omr_prefix_priority_t priority)
    493 {
    494     struct in6_addr prefix;
    495     int prefix_length;
    496     if (publisher->omr_watcher == NULL) {
    497         FAULT("expecting an omr_watcher to be on the publisher");
    498         return true; // Saying yes because we have no watcher and hence can't behave correctly.
    499     }
    500     omr_publisher_prefix_init_from_published(publisher, &prefix, &prefix_length);
    501     return omr_watcher_prefix_wins(publisher->omr_watcher, priority, &prefix, prefix_length);
    502 }
    503 
    504 static bool
    505 omr_publisher_medium_prefix_wins(omr_publisher_t *publisher)
    506 {
    507     return omr_publisher_prefix_wins(publisher, omr_prefix_priority_medium);
    508 }
    509 
    510 static bool
    511 omr_publisher_low_prefix_wins(omr_publisher_t *publisher)
    512 {
    513     return omr_publisher_prefix_wins(publisher, omr_prefix_priority_low);
    514 }
    515 
    516 bool
    517 omr_publisher_publishing_dhcp(omr_publisher_t *publisher)
    518 {
    519     if (publisher->state_header.state == omr_publisher_state_publishing_dhcp)
    520     {
    521         return true;
    522     }
    523     return false;
    524 }
    525 
    526 bool
    527 omr_publisher_publishing_ula(omr_publisher_t *publisher)
    528 {
    529     if (publisher->state_header.state == omr_publisher_state_publishing_ula)
    530     {
    531         return true;
    532     }
    533     return false;
    534 }
    535 
    536 bool
    537 omr_publisher_publishing_prefix(omr_publisher_t *publisher)
    538 {
    539     return omr_publisher_publishing_dhcp(publisher) ||
    540            omr_publisher_publishing_ula(publisher);
    541 }
    542 
    543 static void omr_publisher_queue_run(omr_publisher_t *publisher);
    544 
    545 static void
    546 omr_publisher_prefix_update_callback(void *context, cti_status_t status)
    547 {
    548     omr_publisher_t *publisher = context;
    549     omr_prefix_t *prefix = publisher->publication_queue;
    550 
    551     if (prefix == NULL) {
    552         ERROR("no pending prefix update");
    553         return;
    554     }
    555     SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
    556     INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is in state " PUB_S_SRP ", status = %d",
    557          SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
    558          thread_service_publication_state_name_get(prefix->publication_state), status);
    559     if (status == kCTIStatus_NoError) {
    560         if (prefix->publication_state == add_pending) {
    561             prefix->publication_state = add_complete;
    562             if (publisher->omr_watcher != NULL) {
    563                 omr_watcher_prefix_add(publisher->omr_watcher, &prefix->prefix, prefix->prefix_length, prefix->priority);
    564             }
    565         } else if (prefix->publication_state == delete_pending) {
    566             prefix->publication_state = delete_complete;
    567             if (publisher->omr_watcher != NULL) {
    568                 omr_watcher_prefix_remove(publisher->omr_watcher, &prefix->prefix, prefix->prefix_length);
    569             }
    570         }
    571     } else {
    572         if (prefix->publication_state == add_pending) {
    573             prefix->publication_state = add_failed;
    574         } else if (prefix->publication_state == delete_pending) {
    575             prefix->publication_state = delete_failed;
    576         }
    577     }
    578     publisher->publication_queue = prefix->next;
    579     omr_prefix_release(prefix);
    580     omr_publisher_queue_run(publisher);
    581     RELEASE_HERE(publisher, omr_publisher);
    582 }
    583 
    584 static void
    585 omr_publisher_queue_run(omr_publisher_t *publisher)
    586 {
    587     omr_prefix_t *prefix = publisher->publication_queue;
    588     if (prefix == NULL) {
    589         INFO("the queue is empty.");
    590         // The queue just became empty, so release its reference to the publisher.
    591         RELEASE_HERE(publisher, omr_publisher);
    592         return;
    593     }
    594     if (prefix->publication_state == delete_pending || prefix->publication_state == add_pending) {
    595         INFO("there is a pending update at the head of the queue.");
    596         return;
    597     }
    598     if (prefix->publication_state == want_delete) {
    599         cti_status_t status = cti_remove_prefix(publisher->route_state->srp_server, publisher,
    600                                                 omr_publisher_prefix_update_callback, NULL, &prefix->prefix,
    601                                                 prefix->prefix_length);
    602         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
    603         INFO("removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    604              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    605         if (status != kCTIStatus_NoError) {
    606             ERROR("cti_remove_prefix failed: %d", status);
    607             // For removes, we'll leave it on the queue
    608         } else {
    609             prefix->publication_state = delete_pending;
    610             RETAIN_HERE(publisher, omr_publisher); // for the callback
    611         }
    612     } else if (prefix->publication_state == want_add) {
    613         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
    614         INFO("adding prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    615              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    616         cti_status_t status = cti_add_prefix(publisher->route_state->srp_server, publisher,
    617                                              omr_publisher_prefix_update_callback, NULL, &prefix->prefix,
    618                                              prefix->prefix_length, prefix->onmesh, prefix->preferred, prefix->slaac,
    619                                              prefix->stable, omr_prefix_priority_to_int(prefix->priority));
    620         if (status != kCTIStatus_NoError) {
    621             ERROR("cti_add_prefix failed: %d", status);
    622             publisher->publication_queue = prefix->next;
    623             omr_prefix_release(prefix);
    624         } else {
    625             prefix->publication_state = add_pending;
    626             RETAIN_HERE(publisher, omr_publisher); // for the callback
    627         }
    628     } else {
    629         SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
    630         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is in unexpected state " PUB_S_SRP " on the publication queue",
    631              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
    632              thread_service_publication_state_name_get(prefix->publication_state));
    633         publisher->publication_queue = prefix->next;
    634         omr_prefix_release(prefix);
    635     }
    636 }
    637 
    638 static void
    639 omr_publisher_queue_prefix_update(omr_publisher_t *publisher, struct in6_addr *prefix_address,
    640                                   omr_prefix_priority_t priority, bool preferred,
    641                                   thread_service_publication_state_t initial_state)
    642 {
    643     const int mandatory_subnet_prefix_length = 64;
    644     omr_prefix_t *prefix, **ppref, *old_queue = publisher->publication_queue;
    645     int flags = omr_prefix_flags_generate(true /* onmesh */, preferred, true /* slaac */, priority);
    646 
    647     if (publisher->published_prefix != NULL) {
    648         FAULT("published prefix still present");
    649     }
    650     prefix = omr_prefix_create(prefix_address, mandatory_subnet_prefix_length,
    651                                0 /* metric */, flags, 0 /* rloc */, true /* stable */, false /* ncp */);
    652     if (prefix == NULL) {
    653         ERROR("no memory to remember published prefix!");
    654         return;
    655     }
    656     prefix->publication_state = initial_state;
    657     if (initial_state == want_add) {
    658         publisher->published_prefix = prefix;
    659         omr_prefix_retain(publisher->published_prefix);
    660     }
    661     // Find the end of the queue
    662     for (ppref = &publisher->publication_queue; *ppref != NULL; ppref = &(*ppref)->next)
    663         ;
    664     *ppref = prefix;
    665     // The prefix on the queue is retained by relying on the create/copy rule. When adding a prefix we also retain the
    666     // prefix as publisher->published_prefix, so that retain is always explicit and this retain is always implicit.
    667     // omr_prefix_retain(*ppref);
    668 
    669     // If there is anything in the queue, the queue holds a reference to the publisher, so that it will continue to
    670     // run until it's complete.
    671     if (old_queue == NULL && publisher->publication_queue != NULL) {
    672         RETAIN_HERE(publisher, omr_publisher);
    673     }
    674     omr_publisher_queue_run(publisher);
    675 }
    676 
    677 static void
    678 omr_publisher_publish_prefix(omr_publisher_t *publisher,
    679                              struct in6_addr *prefix_address, omr_prefix_priority_t priority, bool preferred)
    680 {
    681     SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_address, prefix_buf);
    682     INFO("publishing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/64",
    683          SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_address, prefix_buf));
    684     omr_publisher_queue_prefix_update(publisher, prefix_address, priority, preferred, want_add);
    685     INFO("setting publisher->omr_priority to %d, " PUB_S_SRP "preferred", omr_prefix_priority_to_int(priority),
    686          preferred ? "" : "not ");
    687     publisher->omr_priority = priority;
    688 }
    689 
    690 void
    691 omr_publisher_unpublish_prefix(omr_publisher_t *publisher)
    692 {
    693     omr_prefix_t *prefix;
    694 
    695     prefix = publisher->published_prefix;
    696     publisher->published_prefix = NULL;
    697     if (prefix == NULL) {
    698         ERROR("request to unpublished prefix that's not present");
    699         return;
    700     }
    701     SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
    702     INFO("unpublishing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
    703          SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
    704     omr_publisher_queue_prefix_update(publisher, &prefix->prefix, prefix->priority, false, want_delete);
    705     omr_prefix_release(prefix);
    706 }
    707 
    708 static void
    709 omr_publisher_publish_dhcp_prefix(omr_publisher_t *publisher)
    710 {
    711     omr_publisher_install_new_dhcp_prefix(publisher);
    712     omr_publisher_publish_prefix(publisher, &publisher->dhcp_prefix, omr_prefix_priority_medium,
    713                                  publisher->dhcp_preferred_lifetime == 0 ? false : true);
    714     publisher->dhcp_prefix_published = true;
    715     return;
    716 }
    717 
    718 static void
    719 omr_publisher_unpublish_dhcp_prefix(omr_publisher_t *publisher)
    720 {
    721     omr_publisher_unpublish_prefix(publisher);
    722     publisher->dhcp_prefix_published = false;
    723     return;
    724 }
    725 
    726 static void
    727 omr_publisher_publish_ula_prefix(omr_publisher_t *publisher)
    728 {
    729     if (publisher->route_state == NULL || !publisher->route_state->have_thread_prefix) {
    730         ERROR("don't have a thread prefix to publish!");
    731         return;
    732     }
    733     omr_prefix_priority_t priority;
    734     if (publisher->force_publication) {
    735         priority = publisher->force_priority;
    736     } else {
    737         priority = omr_prefix_priority_low;
    738     }
    739     omr_publisher_publish_prefix(publisher, &publisher->route_state->my_thread_ula_prefix, priority, true);
    740     publisher->ula_prefix_published = true;
    741     return;
    742 }
    743 
    744 static void
    745 omr_publisher_unpublish_ula_prefix(omr_publisher_t *publisher)
    746 {
    747     omr_publisher_unpublish_prefix(publisher);
    748     publisher->ula_prefix_published = false;
    749     return;
    750 }
    751 
    752 bool
    753 omr_publisher_have_routable_prefix(omr_publisher_t *publisher)
    754 {
    755     if ((publisher->published_prefix != NULL && omr_watcher_prefix_is_non_ula_prefix(publisher->published_prefix)) ||
    756         (publisher->omr_watcher != NULL && omr_watcher_non_ula_prefix_present(publisher->omr_watcher)))
    757     {
    758         INFO("we have a routable prefix");
    759         return true;
    760     }
    761     INFO("we do not have a routable prefix");
    762     return false;
    763 }
    764 
    765 static state_machine_state_t
    766 omr_publisher_dhcp_event(omr_publisher_t *publisher, state_machine_event_t *UNUSED event)
    767 {
    768     // If we got a DHCP prefix
    769     if (omr_publisher_dhcp_prefix_available(publisher)) {
    770         if (!omr_publisher_medium_or_high_prefix_present(publisher)) {
    771             INFO("got a DHCP prefix, no competing prefixes present");
    772             return omr_publisher_state_publishing_dhcp;
    773         } else {
    774             // If there's a medium or high priority prefix already published, don't publish another.
    775             // This shouldn't happen, but if it does, discontinue DHCP (it should already have been discontinued).
    776             INFO("medium- or high-priority prefix present, so discontinuing DHCP");
    777             omr_publisher_discontinue_dhcp(publisher);
    778         }
    779     }
    780     return omr_publisher_state_invalid;
    781 }
    782 
    783 // In the startup state, we wait to learn about prefixes, or for a timeout to occur. If a prefix shows up that's of medium or
    784 // high priority, we don't need to do DHCP, so go to not_publishing. If it's a low priority prefix, we start DHCP, but won't publish
    785 // anything other than a DHCP prefix.
    786 static state_machine_state_t
    787 omr_publisher_action_startup(state_machine_header_t *state_header, state_machine_event_t *event)
    788 {
    789     STATE_MACHINE_HEADER_TO_PUBLISHER(state_header);
    790     BR_STATE_ANNOUNCE(publisher, event);
    791 
    792     if (event == NULL) {
    793         publisher->omr_priority = omr_prefix_priority_invalid;
    794         if (publisher->wakeup_timer == NULL) {
    795             publisher->wakeup_timer = ioloop_wakeup_create();
    796         }
    797         if (publisher->wakeup_timer == NULL) {
    798             ERROR("unable to allocate a wakeup timer");
    799             return omr_publisher_state_invalid;
    800         }
    801         ioloop_add_wake_event(publisher->wakeup_timer, publisher, omr_publisher_wait_expired,
    802                               omr_publisher_wakeup_release,
    803                               publisher->min_start + srp_random16() % OMR_PUBLISHER_START_WAIT);
    804         publisher->min_start = 0; // Only need mandatory 3-second startup delay when first joining Thread network.
    805         RETAIN_HERE(publisher, omr_publisher); // For wakeup
    806         return omr_publisher_state_invalid;
    807     }
    808     // The only way out of the startup state is for the timer to expire--we don't care about prefixes showing up or
    809     // going away.
    810     if (event->type == state_machine_event_type_timeout) {
    811         INFO("startup timeout");
    812         return omr_publisher_state_check_for_dhcp;
    813     } else {
    814         BR_UNEXPECTED_EVENT(publisher, event);
    815     }
    816 }
    817 
    818 // In this state we haven't seen an OMR prefix yet, but we want to give DHCPv6 PD a chance to succeed before
    819 // configuring an OMR prefix so that we don't push and then remove an OMR prefix. We also land in this state if
    820 // we have seen a low-priority prefix; if we get a DHCP response, we'll publish that, and that should supersede
    821 // the low-priority prefix.
    822 static state_machine_state_t
    823 omr_publisher_action_check_for_dhcp(state_machine_header_t *state_header, state_machine_event_t *event)
    824 {
    825     STATE_MACHINE_HEADER_TO_PUBLISHER(state_header);
    826     BR_STATE_ANNOUNCE(publisher, event);
    827 
    828     if (event == NULL) {
    829         if (publisher->wakeup_timer != NULL) {
    830             ioloop_cancel_wake_event(publisher->wakeup_timer);
    831         } else {
    832             publisher->wakeup_timer = ioloop_wakeup_create();
    833         }
    834         if (publisher->wakeup_timer == NULL) {
    835             ERROR("unable to allocate a wakeup timer");
    836             return omr_publisher_state_invalid;
    837         }
    838         ioloop_add_wake_event(publisher->wakeup_timer, publisher, omr_publisher_wait_expired,
    839                               omr_publisher_wakeup_release, OMR_PUBLISHER_DHCP_SUCCESS_WAIT);
    840         RETAIN_HERE(publisher, omr_publisher); // for the wakeup timer
    841         if (publisher->dhcp_client == NULL) {
    842             omr_publisher_initiate_dhcp(publisher);
    843         }
    844         return omr_publisher_state_invalid;
    845     }
    846     if (event->type == state_machine_event_type_timeout) {
    847         // We didn't get a DHCPv6 prefix quickly (might still get one later)
    848         INFO("timed out waiting for DHCP");
    849         if (omr_publisher_low_prefix_present(publisher)) {
    850             INFO("competing low priority prefix present");
    851             publisher->omr_priority = omr_prefix_priority_low;
    852             return omr_publisher_state_not_publishing;
    853         } else if (omr_publisher_medium_or_high_prefix_present(publisher)) {
    854             INFO("competing medium or high priority prefix present");
    855             omr_publisher_discontinue_dhcp(publisher);
    856             return omr_publisher_state_not_publishing;
    857         }
    858         return omr_publisher_state_publishing_ula;
    859     } else if (event->type == state_machine_event_type_prefix) {
    860         if (omr_publisher_medium_or_high_prefix_present(publisher)) {
    861             INFO("competing medium- or high priority prefix showed up");
    862             // We are trying to get a DHCPv6 prefix, so we need to stop.
    863             if (publisher->dhcp_client != NULL) {
    864                 omr_publisher_discontinue_dhcp(publisher);
    865             }
    866             return omr_publisher_state_not_publishing;
    867         } else if (omr_publisher_low_prefix_present(publisher)) {
    868             INFO("competing low priority prefix showed up");
    869             publisher->omr_priority = omr_prefix_priority_low;
    870             return omr_publisher_state_not_publishing;
    871         } else {
    872             return omr_publisher_state_invalid;
    873         }
    874     } else if (event->type == state_machine_event_type_dhcp) {
    875         return omr_publisher_dhcp_event(publisher, event);
    876     } else {
    877         BR_UNEXPECTED_EVENT(publisher, event);
    878     }
    879 }
    880 
    881 static state_machine_state_t
    882 omr_publisher_action_not_publishing(state_machine_header_t *state_header, state_machine_event_t *event)
    883 {
    884     STATE_MACHINE_HEADER_TO_PUBLISHER(state_header);
    885     BR_STATE_ANNOUNCE(publisher, event);
    886 
    887     if (event == NULL) {
    888         if (publisher->wakeup_timer != NULL) {
    889             ioloop_cancel_wake_event(publisher->wakeup_timer);
    890         }
    891         return omr_publisher_state_invalid;
    892     } else if (event->type == state_machine_event_type_prefix) {
    893         if (!omr_publisher_medium_or_high_prefix_present(publisher)) {
    894             if (publisher->dhcp_client == NULL) {
    895                 INFO("lost competing medium or high-priority prefix");
    896                 return omr_publisher_state_startup;
    897             } else if (!omr_publisher_low_prefix_present(publisher)) {
    898                 INFO("lost competing low-priority prefix.");
    899                 return omr_publisher_state_startup;
    900             }
    901         } else {
    902             // If we were looking for DHCP, stop looking.
    903             if (publisher->dhcp_client != NULL) {
    904                 omr_publisher_discontinue_dhcp(publisher);
    905             }
    906         }
    907         // If we get to here, there is some kind of OMR prefix present, so we don't need to publish one.
    908         return omr_publisher_state_invalid;
    909     } else if (event->type == state_machine_event_type_dhcp) {
    910         return omr_publisher_dhcp_event(publisher, event);
    911     } else {
    912         BR_UNEXPECTED_EVENT(publisher, event);
    913     }
    914 }
    915 
    916 // We enter this state when we decide to publish a prefix we got from DHCP. On entry, we publish the prefix. If a high priority
    917 // prefix shows up, we unpublish it and go to the not_publishing state. If a medium priority prefix shows up, we do an election, since
    918 // that's expected to be a DHCP prefix also. If ours loses, we unpublish and go to not_publishing, otherwise we remain in this state and
    919 // do nothing. If our DHCP prefix goes away and is replaced with a different prefix, we unpublish the old and publish the new. If it just
    920 // goes away and no new prefix is given, then we unpublish the old prefix and go to the publishing_ula state.
    921 static state_machine_state_t
    922 omr_publisher_action_publishing_dhcp(state_machine_header_t *state_header, state_machine_event_t *event)
    923 {
    924     STATE_MACHINE_HEADER_TO_PUBLISHER(state_header);
    925     BR_STATE_ANNOUNCE(publisher, event);
    926     if (event == NULL) {
    927         // Publish the DHCPv6 prefix
    928         omr_publisher_publish_dhcp_prefix(publisher);
    929         return omr_publisher_state_invalid;
    930     } else if (event->type == state_machine_event_type_prefix) {
    931         if (omr_publisher_high_prefix_present(publisher)) {
    932             INFO("deferring to high-priority prefix");
    933             omr_publisher_unpublish_dhcp_prefix(publisher);
    934             omr_publisher_discontinue_dhcp(publisher);
    935             return omr_publisher_state_not_publishing;
    936         } else if (omr_publisher_medium_prefix_present(publisher)) {
    937             if (!omr_publisher_medium_prefix_wins(publisher)) {
    938                 INFO("deferring to winning medium-priority prefix");
    939                 omr_publisher_unpublish_dhcp_prefix(publisher);
    940                 omr_publisher_discontinue_dhcp(publisher);
    941                 return omr_publisher_state_not_publishing;
    942             }
    943             return omr_publisher_state_invalid;
    944         }
    945         return omr_publisher_state_invalid;
    946     } else if (event->type == state_machine_event_type_dhcp) {
    947         if (omr_publisher_dhcp_prefix_lost(publisher)) {
    948             INFO("DHCP prefix withdrawn");
    949             omr_publisher_unpublish_dhcp_prefix(publisher);
    950             return omr_publisher_state_publishing_ula;
    951         } else if (omr_publisher_dhcp_prefix_changed(publisher)) {
    952             INFO("new prefix from DHCP server");
    953             omr_publisher_unpublish_dhcp_prefix(publisher);
    954             omr_publisher_publish_dhcp_prefix(publisher);
    955             return omr_publisher_state_invalid;
    956         }
    957         return omr_publisher_state_invalid;
    958     } else {
    959         BR_UNEXPECTED_EVENT(publisher, event);
    960     }
    961 }
    962 
    963 // We enter this state when no prefix showed up while we were waiting, and no DHCP prefix was given, or when we've unpublished
    964 // our own DHCP prefix because it was withdrawn or expired. In order for this to happen, it has to be the case that no competing
    965 // prefix is being published, so we're going to publish our prefix unconditionally. It's possible that some other BR will publish
    966 // a DHCP prefix or a priority prefix, in which case we unpublish ours and go to not_publishing. It's also possible that another BR
    967 // will publish a ULA prefix (low priority). In this case, we do an election, and if we lose, we unpublish ours and go to
    968 // not_publishing.
    969 static state_machine_state_t
    970 omr_publisher_action_publishing_ula(state_machine_header_t *state_header, state_machine_event_t *event)
    971 {
    972     STATE_MACHINE_HEADER_TO_PUBLISHER(state_header);
    973     BR_STATE_ANNOUNCE(publisher, event);
    974     if (event == NULL) {
    975         omr_publisher_publish_ula_prefix(publisher);
    976         if (publisher->dhcp_client == NULL) {
    977             FAULT("no DHCP client running!");
    978             omr_publisher_initiate_dhcp(publisher);
    979             publisher->omr_priority = omr_prefix_priority_low;
    980         }
    981         return omr_publisher_state_invalid;
    982     } else if (event->type == state_machine_event_type_prefix) {
    983         if (publisher->force_publication) {
    984             INFO("ignoring potentially competing prefix");
    985             return omr_publisher_state_invalid;
    986         }
    987         if (omr_publisher_medium_or_high_prefix_present(publisher)) {
    988             INFO("deferring to medium- or high-priority prefix");
    989             omr_publisher_unpublish_ula_prefix(publisher);
    990             return omr_publisher_state_not_publishing;
    991         } else if (omr_publisher_low_prefix_present(publisher)) {
    992             if (!omr_publisher_low_prefix_wins(publisher)) {
    993                 INFO("deferring to winning low-priority prefix");
    994                 omr_publisher_unpublish_ula_prefix(publisher);
    995                 publisher->omr_priority = omr_prefix_priority_low;
    996                 return omr_publisher_state_not_publishing;
    997             }
    998             return omr_publisher_state_invalid;
    999         }
   1000         return omr_publisher_state_invalid;
   1001     } else if (event->type == state_machine_event_type_dhcp) {
   1002         if (publisher->force_publication) {
   1003             INFO("ignoring dhcp prefix");
   1004             return omr_publisher_state_invalid;
   1005         } else if (omr_publisher_dhcp_prefix_available(publisher)) {
   1006             INFO("switching to DHCP prefix");
   1007             omr_publisher_unpublish_ula_prefix(publisher);
   1008             return omr_publisher_state_publishing_dhcp;
   1009         }
   1010         return omr_publisher_state_invalid;
   1011     } else {
   1012         BR_UNEXPECTED_EVENT(publisher, event);
   1013     }
   1014 }
   1015 
   1016 void
   1017 omr_publisher_check_prefix(omr_publisher_t *publisher, struct in6_addr *prefix, int UNUSED len)
   1018 {
   1019     if (publisher == NULL) {
   1020         return;
   1021     }
   1022     if (publisher->published_prefix == NULL) {
   1023         return;
   1024     }
   1025     // Make sure that this prefix, which we are seeing advetised on infrastructure, is not published as the OMR prefix.
   1026     if (!in6prefix_compare(&publisher->published_prefix->prefix, prefix, 8)) {
   1027         if (!in6prefix_compare(&publisher->ula_prefix, prefix, 8)) {
   1028             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->s6_addr, prefix_buf);
   1029             FAULT("ULA prefix is being advertised on infrastructure: " PRI_SEGMENTED_IPv6_ADDR_SRP,
   1030                   SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->s6_addr, prefix_buf));
   1031         } else {
   1032             // If we get here it means that our DHCP prefix is bogus and we can't use it. So we're going to block DHCP, and treat this as
   1033             // a DHCP prefix loss.
   1034             SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->s6_addr, prefix_buf);
   1035             ERROR("DHCP prefix is being advertised on infrastructure: " PRI_SEGMENTED_IPv6_ADDR_SRP,
   1036                   SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->s6_addr, prefix_buf));
   1037 
   1038             publisher->dhcp_wanted = false;
   1039             publisher->dhcp_blocked = true;
   1040             omr_publisher_dhcp_client_deactivate(publisher, (intptr_t)publisher->dhcp_client);
   1041             omr_publisher_send_dhcp_event(publisher, NULL, 0, 0);
   1042         }
   1043     }
   1044 }
   1045 
   1046 // Local Variables:
   1047 // mode: C
   1048 // tab-width: 4
   1049 // c-file-style: "bsd"
   1050 // c-basic-offset: 4
   1051 // fill-column: 120
   1052 // indent-tabs-mode: nil
   1053 // End:
   1054