Kenji Arai / TYBLE16_mbedlized_os5_several_examples_1st

Dependencies:   nRF51_Vdd TextLCD BME280

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers etx.c Source File

etx.c

00001 /*
00002  * Copyright (c) 2014-2018, Arm Limited and affiliates.
00003  * SPDX-License-Identifier: Apache-2.0
00004  *
00005  * Licensed under the Apache License, Version 2.0 (the "License");
00006  * you may not use this file except in compliance with the License.
00007  * You may obtain a copy of the License at
00008  *
00009  *     http://www.apache.org/licenses/LICENSE-2.0
00010  *
00011  * Unless required by applicable law or agreed to in writing, software
00012  * distributed under the License is distributed on an "AS IS" BASIS,
00013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014  * See the License for the specific language governing permissions and
00015  * limitations under the License.
00016  */
00017 #include "nsconfig.h"
00018 #include "ns_types.h"
00019 #include "common_functions.h"
00020 #include "ns_trace.h"
00021 #include "string.h"
00022 #include "nsdynmemLIB.h"
00023 #include "platform/arm_hal_phy.h"
00024 #include "net_interface.h"
00025 
00026 #include "Core/include/address.h"
00027 #include "MLE/mle.h"
00028 #include "NWK_INTERFACE/Include/protocol.h"
00029 #include "NWK_INTERFACE/Include/protocol_stats.h"
00030 #include "Service_Libs/etx/etx.h"
00031 #include "Service_Libs/mac_neighbor_table/mac_neighbor_table.h"
00032 #include "Service_Libs/utils/isqrt.h"
00033 
00034 //TODO: Refactor this away!
00035 #include "MAC/rf_driver_storage.h"
00036 
00037 #define TRACE_GROUP "etx"
00038 
00039 static uint16_t etx_current_calc(uint16_t etx, uint8_t accumulated_failures);
00040 static uint16_t etx_dbm_lqi_calc(uint8_t lqi, int8_t dbm);
00041 static void etx_value_change_callback_needed_check(uint16_t etx, uint16_t *stored_diff_etx, uint8_t accumulated_failures, uint8_t attribute_index);
00042 static void etx_accum_failures_callback_needed_check(etx_storage_t * entry, uint8_t attribute_index);
00043 
00044 typedef struct {
00045     uint16_t hysteresis;                            // 12 bit fraction
00046     uint8_t accum_threshold;
00047     etx_value_change_handler_t *callback_ptr;
00048     etx_accum_failures_handler_t *accum_cb_ptr;
00049     etx_storage_t *etx_storage_list;
00050     uint8_t ext_storage_list_size;
00051     int8_t interface_id;
00052 } ext_info_t;
00053 
00054 static ext_info_t etx_info = {
00055     .hysteresis = 0,
00056     .accum_threshold = 0,
00057     .callback_ptr = NULL,
00058     .accum_cb_ptr = NULL,
00059     .etx_storage_list = NULL,
00060     .ext_storage_list_size = 0,
00061     .interface_id = -1
00062 };
00063 
00064 /**
00065  * \brief A function to update ETX value based on transmission attempts
00066  *
00067  *  Update is made based on failed and successful message sending
00068  *  attempts for a message.
00069  *
00070  * \param attempts number of attempts to send message
00071  * \param success was message sending successful
00072  * \param addr_type address type, ADDR_802_15_4_SHORT or ADDR_802_15_4_LONG
00073  * \param addr_ptr PAN ID with 802.15.4 address
00074  */
00075 void etx_transm_attempts_update(int8_t interface_id, uint8_t attempts, bool success, uint8_t attribute_index)
00076 {
00077     uint32_t etx;
00078     uint8_t accumulated_failures;
00079     // Gets table entry
00080     etx_storage_t * entry = etx_storage_entry_get(interface_id, attribute_index);
00081     if (!entry) {
00082         return;
00083     }
00084 
00085     accumulated_failures = entry->accumulated_failures;
00086 
00087     if (!success) {
00088         /* Stores failed attempts to estimate ETX and to calculate
00089            new ETX after successful sending */
00090         if (accumulated_failures + attempts < 32) {
00091             entry->accumulated_failures += attempts;
00092         } else {
00093             success = true;
00094         }
00095     }
00096 
00097     if (success) {
00098         entry->accumulated_failures = 0;
00099     } else {
00100         etx_accum_failures_callback_needed_check(entry, attribute_index);
00101     }
00102 
00103     if (entry->etx) {
00104         // If hysteresis is set stores ETX value for comparison
00105         if (etx_info.hysteresis && !entry->stored_diff_etx) {
00106             entry->stored_diff_etx = entry->etx;
00107         }
00108 
00109         if (success) {
00110             // ETX = 7/8 * current ETX + 1/8 * ((attempts + failed attempts) << 12)
00111             etx = entry->etx - (entry->etx >> ETX_MOVING_AVERAGE_FRACTION);
00112             etx += (attempts + accumulated_failures) << (12 - ETX_MOVING_AVERAGE_FRACTION);
00113 
00114             if (etx > 0xffff) {
00115                 entry->etx = 0xffff;
00116             } else {
00117                 entry->etx = etx;
00118             }
00119         }
00120 
00121         // If real ETX value has been received do not update based on LQI or dBm
00122         entry->tmp_etx = false;
00123 
00124         // Checks if ETX value change callback is needed
00125         etx_value_change_callback_needed_check(entry->etx, &(entry->stored_diff_etx), entry->accumulated_failures, attribute_index);
00126     }
00127 }
00128 
00129 /**
00130  * \brief A function to update ETX value based on remote incoming IDR
00131  *
00132  *  Update is made based on remote incoming IDR received from
00133  *  neighbor.
00134  *
00135  * \param remote_incoming_idr Remote incoming IDR
00136  * \param mac64_addr_ptr long MAC address
00137  */
00138 void etx_remote_incoming_idr_update(int8_t interface_id, uint8_t remote_incoming_idr, uint8_t attribute_index)
00139 {
00140     etx_storage_t * entry = etx_storage_entry_get(interface_id, attribute_index);
00141 
00142     if (entry) {
00143         // If ETX has been set
00144         if (entry->etx) {
00145             // If hysteresis is set stores ETX value to enable comparison
00146             if (etx_info.hysteresis && !entry->stored_diff_etx) {
00147                 entry->stored_diff_etx = entry->etx;
00148             }
00149             // remote EXT = remote incoming IDR^2 (12 bit fraction)
00150             uint32_t remote_ext = ((uint32_t)remote_incoming_idr * remote_incoming_idr) << 2;
00151 
00152             // ETX = 7/8 * current ETX + 1/8 * remote ETX */
00153             uint32_t etx = entry->etx - (entry->etx >> ETX_MOVING_AVERAGE_FRACTION);
00154             etx += remote_ext >> ETX_MOVING_AVERAGE_FRACTION;
00155 
00156             if (etx > 0xffff) {
00157                 entry->etx = 0xffff;
00158             } else {
00159                 entry->etx = etx;
00160             }
00161 
00162             // Checks if ETX value change callback is needed
00163             etx_value_change_callback_needed_check(entry->etx, &(entry->stored_diff_etx), entry->accumulated_failures, attribute_index);
00164         }
00165         entry->remote_incoming_idr = remote_incoming_idr;
00166     }
00167 }
00168 
00169 /**
00170  * \brief A function to read ETX value
00171  *
00172  *  Returns ETX value for an address
00173  *
00174  * \param interface_id network interface id
00175  * \param addr_type address type, ADDR_802_15_4_SHORT or ADDR_802_15_4_LONG
00176  * \param addr_ptr PAN ID with 802.15.4 address
00177  *
00178  * \return 0x0100 to 0xFFFF ETX value (8 bit fraction)
00179  * \return 0xFFFF address not associated
00180  * \return 0x0000 address unknown or other error
00181  * \return 0x0001 no ETX statistics on this interface
00182  */
00183 uint16_t etx_read(int8_t interface_id, addrtype_t addr_type, const uint8_t *addr_ptr)
00184 {
00185     protocol_interface_info_entry_t *interface = protocol_stack_interface_info_get_by_id(interface_id);
00186 
00187     if (!addr_ptr || !interface) {
00188         return 0;
00189     }
00190 
00191     if (interface->etx_read_override) {
00192         // Interface has modified ETX calculation
00193         return interface->etx_read_override(interface, addr_type, addr_ptr);
00194     }
00195 
00196     uint8_t attribute_index;
00197     if (interface->nwk_id == IF_IPV6) {
00198         return 1;
00199     }
00200 
00201     //Must Support old MLE table and new still same time
00202     mac_neighbor_table_entry_t *mac_neighbor = mac_neighbor_table_address_discover(mac_neighbor_info(interface), addr_ptr + PAN_ID_LEN, addr_type);
00203     if (!mac_neighbor) {
00204         return 0xffff;
00205     }
00206     attribute_index = mac_neighbor->index ;
00207 
00208 
00209     //tr_debug("Etx Read from atribute %u", attribute_index);
00210 
00211     etx_storage_t * entry = etx_storage_entry_get(interface_id, attribute_index);
00212 
00213     if (!entry) {
00214         return 0xffff;
00215     }
00216 
00217     uint16_t etx  = etx_current_calc(entry->etx, entry->accumulated_failures);
00218     etx >>= 4;
00219 
00220     //tr_debug("Etx value %u", etx);
00221 
00222     return etx;
00223 }
00224 
00225 /**
00226  * \brief A function to read local incoming IDR value
00227  *
00228  *  Returns local incoming IDR value for an address
00229  *
00230  * \param mac64_addr_ptr long MAC address
00231  *
00232  * \return 0x0100 to 0xFFFF incoming IDR value (8 bit fraction)
00233  * \return 0x0000 address unknown
00234  */
00235 uint16_t etx_local_incoming_idr_read(int8_t interface_id, uint8_t attribute_index)
00236 {
00237     uint32_t local_incoming_idr = 0;
00238     etx_storage_t * entry = etx_storage_entry_get(interface_id, attribute_index);
00239     if (entry) {
00240         uint16_t local_etx = etx_current_calc(entry->etx, entry->accumulated_failures);
00241 
00242         local_incoming_idr = isqrt32((uint32_t)local_etx << 16);
00243         // divide by sqrt(2^12)
00244         local_incoming_idr = local_incoming_idr >> 6;
00245     }
00246 
00247     return local_incoming_idr;
00248 }
00249 
00250 /**
00251  * \brief A function to read local incoming IDR value
00252  *
00253  *  Returns local incoming IDR value for an address
00254  *
00255  * \param mac64_addr_ptr long MAC address
00256  *
00257  * \return 0x0100 to 0xFFFF incoming IDR value (8 bit fraction)
00258  * \return 0x0000 address unknown
00259  */
00260 uint16_t etx_local_etx_read(int8_t interface_id, uint8_t attribute_index)
00261 {
00262     etx_storage_t * entry = etx_storage_entry_get(interface_id, attribute_index);
00263     if (!entry) {
00264         return 0;
00265     }
00266     return etx_current_calc(entry->etx, entry->accumulated_failures) >> 4;
00267 }
00268 
00269 /**
00270  * \brief A function to calculate current ETX
00271  *
00272  *  Returns current ETX value based on ETX and failed attempts. Return
00273  *  value is scaled by scaling factor
00274  *
00275  * \param etx ETX (12 bit fraction)
00276  * \param accumulated_failures failed attempts
00277  *
00278  * \return ETX value (12 bit fraction)
00279  */
00280 static uint16_t etx_current_calc(uint16_t etx, uint8_t accumulated_failures)
00281 {
00282     uint32_t current_etx;
00283 
00284     // If there is no failed attempts
00285     if (accumulated_failures == 0) {
00286         current_etx = etx;
00287     } else {
00288         /* Calculates ETX estimate based on failed attempts
00289            ETX = current ETX + 1/8 * (failed attempts << 12) */
00290         current_etx = etx + (accumulated_failures << (12 - ETX_MOVING_AVERAGE_FRACTION));
00291         if (current_etx > 0xffff) {
00292             current_etx = 0xffff;
00293         }
00294     }
00295 
00296     return current_etx;
00297 }
00298 
00299 /**
00300  * \brief A function to update ETX value based on LQI and dBm
00301  *
00302  *  Update is made based on dBM and LQI of received message.
00303  *
00304  * \param lqi link quality indicator
00305  * \param dbm measured dBm
00306  * \param mac64_addr_ptr long MAC address
00307  *
00308  * \return 0x0100 to 0xFFFF local incoming IDR value (8 bit fraction)
00309  */
00310 uint16_t etx_lqi_dbm_update(int8_t interface_id, uint8_t lqi, int8_t dbm, uint8_t attribute_index)
00311 {
00312     uint32_t local_incoming_idr = 0;
00313     uint32_t etx = 0;
00314 
00315     etx_storage_t *entry = etx_storage_entry_get(interface_id, attribute_index);
00316 
00317     if (entry) {
00318         // If local ETX is not set calculate it based on LQI and dBm
00319         if (!entry->etx) {
00320             etx = etx_dbm_lqi_calc(lqi, dbm);
00321             entry->etx = etx;
00322             entry->tmp_etx = true;
00323         }
00324         // If local ETX has been calculated without remote incoming IDR and
00325         // remote incoming IDR is available update it by remote incoming IDR value
00326         if (entry->remote_incoming_idr && entry->tmp_etx) {
00327             entry->tmp_etx = false;
00328 
00329             local_incoming_idr = isqrt32((uint32_t)entry->etx << 16);
00330             // divide by sqrt(2^12) and scale to 12 bit fraction
00331             local_incoming_idr = local_incoming_idr >> 2;
00332 
00333             etx = local_incoming_idr * (((uint16_t)entry->remote_incoming_idr) << 7);
00334             entry->etx = etx >> 12;
00335 
00336             local_incoming_idr >>= 4;
00337         }
00338 
00339         // If local ETX has been calculated indicates new neighbor
00340         if (etx) {
00341             etx_neighbor_add(interface_id, attribute_index);
00342         }
00343     }
00344 
00345     // If local ETX is not set return temporary ETX based on LQI and dB,
00346     if (!local_incoming_idr) {
00347         if (!etx) {
00348             etx = etx_dbm_lqi_calc(lqi, dbm);
00349         }
00350 
00351         local_incoming_idr = isqrt32(etx << 16);
00352         // divide by sqrt(2^12)
00353         local_incoming_idr >>= 6;
00354     }
00355 
00356     return local_incoming_idr;
00357 }
00358 
00359 /**
00360  * \brief A function to calculate ETX value based on dBm and LQI
00361  *
00362  *  Calculation is made using RF driver service. If service does not
00363  *  exists then local function is used.
00364  *
00365  * \param lqi link quality indicator
00366  * \param dbm measured dBm
00367  *
00368  * \return ETX value (12 bit fraction)
00369  */
00370 static uint16_t etx_dbm_lqi_calc(uint8_t lqi, int8_t dbm)
00371 {
00372     protocol_interface_info_entry_t *cur = 0;
00373     int8_t driver_ret_value = -1;
00374     uint16_t etx;
00375 
00376     phy_signal_info_s signal_info;
00377     signal_info.type = PHY_SIGNAL_INFO_ETX;
00378     signal_info.lqi = lqi;
00379     signal_info.dbm = dbm;
00380     signal_info.result = 0xffff;
00381 
00382     //TODO: This is needed, but RF driver cannot be accessed directly! Figure out MAC extension for this.
00383     cur = protocol_stack_interface_info_get(IF_6LoWPAN);
00384     if ((cur) && (cur->dev_driver) && (cur->dev_driver->phy_driver)) {
00385         phy_device_driver_s *dev_driver = cur->dev_driver->phy_driver;
00386         if (dev_driver->extension) {
00387             driver_ret_value = dev_driver->extension(PHY_EXTENSION_CONVERT_SIGNAL_INFO, (uint8_t *)&signal_info);
00388         }
00389     }
00390 
00391     if ((driver_ret_value != -1) && (signal_info.result != 0xffff)) {
00392         etx = signal_info.result;
00393         etx <<= 4;
00394     } else {
00395        /* Atmel version
00396           dBm = RSSI base value [dBm] + 1.03 [dB] x ED level
00397           LQI = errors in received frame */
00398 
00399        // for dBm -90 and LQI 0 ETX will be 2.4
00400        etx = ((dbm * -1) * (256 - lqi));
00401        etx >>= 1; // scale result to 12 bits
00402        etx += 1 << 12; // add one (perfect link)
00403     }
00404 
00405     return etx;
00406 }
00407 
00408 /**
00409  * \brief A function to register ETX value change callback
00410  *
00411  *  Register ETX value change callback. When ETX value has changed more or equal
00412  *  to hysteresis value ETX module calls ETX value change callback.
00413  *
00414  * \param nwk_interface_id network interface id
00415  * \param hysteresis hysteresis value (8 bit fraction)
00416  * \param callback_ptr callback function pointer
00417  *
00418  * \return 0 not 6LowPAN interface
00419  * \return 1 success
00420  */
00421 uint8_t etx_value_change_callback_register(nwk_interface_id nwk_id, int8_t interface_id, uint16_t hysteresis, etx_value_change_handler_t *callback_ptr)
00422 {
00423     if ((nwk_id == IF_6LoWPAN) && hysteresis && callback_ptr) {
00424         etx_info.hysteresis = hysteresis << 4;
00425         etx_info.callback_ptr = callback_ptr;
00426         etx_info.interface_id = interface_id;
00427         return 1;
00428     } else {
00429         return 0;
00430     }
00431 }
00432 
00433 bool etx_storage_list_allocate(int8_t interface_id, uint8_t etx_storage_size)
00434 {
00435     if (!etx_storage_size) {
00436         ns_dyn_mem_free(etx_info.etx_storage_list);
00437         etx_info.etx_storage_list = NULL;
00438         etx_info.ext_storage_list_size = 0;
00439         return true;
00440     }
00441 
00442     if (etx_info.ext_storage_list_size == etx_storage_size) {
00443         return true;
00444     }
00445 
00446     ns_dyn_mem_free(etx_info.etx_storage_list);
00447     etx_info.ext_storage_list_size = 0;
00448     etx_info.etx_storage_list = ns_dyn_mem_alloc(sizeof(etx_storage_t) * etx_storage_size);
00449     if (!etx_info.etx_storage_list) {
00450         return false;
00451     }
00452 
00453     etx_info.ext_storage_list_size = etx_storage_size;
00454     etx_info.interface_id = interface_id;
00455     etx_storage_t * list_ptr = etx_info.etx_storage_list;
00456     for (uint8_t i = 0; i< etx_storage_size; i++) {
00457         memset(list_ptr, 0, sizeof(etx_storage_t));
00458 
00459         list_ptr++;
00460     }
00461     return true;
00462 
00463 }
00464 
00465 etx_storage_t *etx_storage_entry_get(int8_t interface_id, uint8_t attribute_index)
00466 {
00467     if (etx_info.interface_id != interface_id || !etx_info.etx_storage_list || attribute_index >= etx_info.ext_storage_list_size) {
00468         tr_debug("Unknow ID or un initilized ETX %u", attribute_index);
00469         return NULL;
00470     }
00471 
00472     etx_storage_t *entry = etx_info.etx_storage_list + attribute_index;
00473     return entry;
00474 }
00475 
00476 
00477 /**
00478  * \brief A function to register accumulated failures callback
00479  *
00480  *  When the number of accumulated failures has reached the threshold
00481  *  value, the ETX module calls the accumulated failures callback on
00482  *  every transmission failure.
00483  *
00484  * \param nwk_id network ID (6LoWPAN)
00485  * \param interface_id interface ID
00486  * \param threshold threshold value for accumulated failures
00487  * \param callback_ptr callback function pointer
00488  *
00489  * \return 0 not 6LowPAN interface
00490  * \return 1 success
00491  */
00492 uint8_t etx_accum_failures_callback_register(nwk_interface_id nwk_id, int8_t interface_id, uint8_t threshold, etx_accum_failures_handler_t *callback_ptr)
00493 {
00494     if ((nwk_id == IF_6LoWPAN) && threshold && callback_ptr) {
00495         etx_info.interface_id = interface_id;
00496         etx_info.accum_threshold = threshold;
00497         etx_info.accum_cb_ptr = callback_ptr;
00498         return 1;
00499     } else {
00500         return 0;
00501     }
00502 }
00503 
00504 /**
00505  * \brief A function to check if ETX value change callback is needed
00506  *
00507  *  Calculates current ETX and compares it against stored ETX. If change
00508  *  of the values is more than hysteresis calls ETX value change
00509  *  callback.
00510  *
00511  * \param etx ETX (12 bit fraction)
00512  * \param stored_diff_etx stored ETX value
00513  * \param accumulated_failures failed attempts
00514  * \param mac64_addr_ptr long MAC address
00515  * \param mac16_addr short MAC address or 0xffff address is not set
00516  *
00517  * \return ETX value (12 bit fraction)
00518  */
00519 static void etx_value_change_callback_needed_check(uint16_t etx, uint16_t *stored_diff_etx, uint8_t accumulated_failures, uint8_t attribute_index)
00520 {
00521     uint16_t current_etx;
00522     bool callback = false;
00523     if (!etx_info.hysteresis) {
00524         return;
00525     }
00526 
00527     // Calculates current ETX
00528     current_etx = etx_current_calc(etx, accumulated_failures);
00529 
00530     // If difference is more than hysteresis
00531     if (current_etx > *stored_diff_etx) {
00532         if (current_etx - *stored_diff_etx >= etx_info.hysteresis) {
00533             callback = true;
00534         }
00535     } else if (current_etx < *stored_diff_etx) {
00536         if (*stored_diff_etx - current_etx >= etx_info.hysteresis) {
00537             callback = true;
00538         }
00539     }
00540 
00541     // Calls callback function
00542     if (callback) {
00543         etx_info.callback_ptr(etx_info.interface_id, (*stored_diff_etx) >> 4, current_etx >> 4, attribute_index);
00544         *stored_diff_etx = current_etx;
00545     }
00546 }
00547 
00548 /**
00549  * \brief A function to check if accumulated failures callback is needed
00550  *
00551  *  If the current number of accumulated failures is equal or greater than
00552  *  the set threshold value, the function calls accumulated failures callback.
00553  *
00554  * \param neigh_table_ptr the neighbor node in question
00555  */
00556 static void etx_accum_failures_callback_needed_check(etx_storage_t * entry, uint8_t attribute_index)
00557 {
00558     if (!etx_info.accum_threshold) {
00559         return;
00560     }
00561 
00562     if (entry->accumulated_failures < etx_info.accum_threshold) {
00563         return;
00564     }
00565 
00566     etx_info.accum_cb_ptr(etx_info.interface_id, entry->accumulated_failures, attribute_index);
00567 }
00568 
00569 /**
00570  * \brief A function to remove ETX neighbor
00571  *
00572  *  Notifies ETX module that neighbor has been removed. Calls ETX value change callback
00573  *  if that is set.
00574  *
00575  * \param mac64_addr_ptr long MAC address
00576  *
00577  */
00578 void etx_neighbor_remove(int8_t interface_id, uint8_t attribute_index) {
00579 
00580     tr_debug("Remove attribute %u", attribute_index);
00581     uint16_t stored_diff_etx;
00582     etx_storage_t *entry = etx_storage_entry_get(interface_id, attribute_index);
00583     if (entry && etx_info.callback_ptr) {
00584 
00585         if (entry->etx) {
00586             stored_diff_etx = entry->stored_diff_etx >> 4;
00587             if (!stored_diff_etx) {
00588                 stored_diff_etx = 0xffff;
00589             }
00590             etx_info.callback_ptr(etx_info.interface_id, stored_diff_etx, 0xffff, attribute_index);
00591         }
00592         //Clear all data base back to zero for new user
00593         memset(entry, 0, sizeof(etx_storage_t));
00594     }
00595 }
00596 
00597 /**
00598  * \brief A function to add ETX neighbor
00599  *
00600  *  Notifies ETX module that neighbor has been added. Calls ETX value change callback
00601  *  if that is set.
00602  *
00603  * \param mac64_addr_ptr long MAC address
00604  *
00605  */
00606 void etx_neighbor_add(int8_t interface_id, uint8_t attribute_index) {
00607 
00608     tr_debug("Add attribute %u", attribute_index);
00609     uint16_t stored_diff_etx;
00610     etx_storage_t *entry = etx_storage_entry_get(interface_id, attribute_index);
00611     if (entry && etx_info.callback_ptr) {
00612         // Gets table entry
00613 
00614         if (entry->etx) {
00615             stored_diff_etx = entry->stored_diff_etx;
00616             if (!stored_diff_etx) {
00617                 stored_diff_etx = entry->etx;
00618             }
00619             etx_info.callback_ptr(etx_info.interface_id, stored_diff_etx >> 4, entry->etx >> 4,
00620                                   attribute_index);
00621         }
00622     }
00623 }