Tuesday, October 20, 2009

Section 27.1.  Main Data Structures










27.1. Main Data Structures





















To understand the code for the neighboring infrastructure, we first need to describe a few data structures used heavily in the neighboring subsystem, and see how they interact with each other.


Most of the definitions for these structures can be found in the file include/net/neighbour.h. Note that the Linux kernel code uses the British spelling neighbour for data structures and functions related to this subsystem. When speaking generically of neighbors, this book sticks to the American spelling, which is the spelling found in RFCs and other official documents.




struct neighbour


Stores information about a neighbor, such as the L2 and L3 addresses, the NUD state, the device through which the neighbor can be reached, etc. Note that a neighbour enTRy is associated not with a host, but with an L3 address. There can be more than one L3 address for a host. For example, routers, among other systems, have multiple interfaces and therefore multiple L3 addresses.



struct neigh_table


Describes a neighboring protocol's parameters and functions. There is one instance of this structure for each neighboring protocol. All of the structures are inserted into a global list pointed to by the static variable neigh_tables and protected by the lock neigh_tbl_lock. This lock protects the integrity of the list, but not the content of each entry.



struct neigh_parms


A set of parameters that can be used to tune the behavior of a neighboring protocol on a per-device basis. Since more than one protocol can be enabled on most interfaces (for instance, IPv4 and IPv6), more than one neigh_parms structure can be associated with a net_device structure.



struct neigh_ops


A set of functions that represents the interface between the L3 protocols such as IP and dev_queue_xmit, the API introduced in Chapter 11 and described briefly in the upcoming section "Common Interface Between L3 Protocols and Neighboring Protocols." The virtual functions can change based on the context in which they are used (that is, on the status of the neighbor, as described in Chapter 26).



struct hh_cache


Caches link layer headers to speed up transmission. It is faster to copy a cached header into a buffer in one shot than to fill in its fields one by one. Not all device drivers implement header caching. See the section "L2 Header Caching."



struct rtable




struct dst_entry


When a host needs to route a packet, it first consults its cache and then, in the case of a cache miss, it queries the routing table. Every time the host queries the routing table, the result is saved into the cache. The IPv4 routing cache is composed of rtable structures. Each instance is associated with a different destination IP address. Among the fields of the rtable structure are the destination address, the next hop (router), and a structure of type dst_entry that is used to store the protocol-independent information. dst_entry includes a pointer to the neighbour structure associated with the next hop. I cover the dst_entry data structure in detail in Chapter 36. In the rest of this chapter, I will often refer to dst_entry structures as elements of the routing table cache, even though dst_entry is actually only a field of the rtable structure.


Figure 27-1 shows how dst_entry structures are linked to hh_cache and neighbour structures.


The neighboring code also uses some other small data structures. For instance, struct pneigh_entry is used by destination-based proxying, and struct neigh_statistics is used to collect statistics about neighboring protocols. The first structure is described in the section "Acting As a Proxy," and the second one is described in the section "Statistics" in Chapter 29. Figure 27-2 also includes the following data structure types, described in greater detail in Chapters 22 and 23:



Figure 27-1. Relationship among dst_entry, neighbour, and hh_cache structures





in_device, inet6_dev


Used to store the IPv4 and IPv6 configurations of a device, respectively.



net_device


There is one net_device structure for each network device recognized by the kernel. See Chapter 8.


Figure 27-2 shows the relationships between the most important data structures. Right now it might seem a big mess, but it will make much more sense by the end of this chapter.


Here are the main points shown in Figure 27-2:


  • In the central part of the figure, you can see that each network device has a pointer to a data structure that holds the configuration for each L3 protocol configured on the device. In the example shown in the figure, IPv6 is configured on one device and IPv4 is configured on both. Both the in_device structure (IPv4 configuration) and inet6_dev structure (IPv6 configuration) include a pointer to the configuration used by their neighboring protocols, respectively ARP and ND.

    All of the neigh_parms structures used by any given protocol are linked together in a unidirectional list whose root is stored in the protocol's neigh_table structure.

  • The top and bottom of the figure show that each protocol keeps two hash tables. The first one, hash_buckets, caches the L3-to-L2 mappings resolved by the protocol or statically configured. The second one, phash_bucket, stores those IP addresses that are proxied, as described in the section "Per-Device Proxying and Per-Destination Proxying." Note that phash_bucket is not a cache, so its elements do not expire and don't need confirmation. Each pneigh_entry structure


    Figure 27-2. Data structures' relationships

    includes a pointer (not depicted in Figure 27-2) to its associated net_device structure. Figure 27-6 gives more detail on the structure of the cache hash_buckets.

  • Each neighbour instance is associated with one or more hh_cache structures, if the device supports header caching. The section "L2 Header Caching," and Figures 27-1 and 27-10, give more details about the relationship between neighbour and hh_cache structures.












10.2 Conditional Compilation




I l@ve RuBoard










10.2 Conditional Compilation





One problem programmers have is
writing code that can work on many different machines. In theory, C++
code is portable; in practice, many machines have little quirks that
must be accounted for. For example, this book covers Unix, MS-DOS,
and Windows compilers. Although they are almost the same, there are
some differences.



Through the use of conditional
compilation, the preprocessor allows you great
flexibility in changing the way code is generated. Suppose you want
to put debugging code in the program while you are working on it and
then remove the debugging code in the production version. You could
do this by including the code in an #ifdef-#endif section, like this:



#ifdef DEBUG 
std::cout << "In compute_hash, value " << value << " hash " << hash << "\n";
#endif /* DEBUG */









You do not have to put the /* DEBUG */ after the
#endif, but it is very useful as a
comment.




If the beginning of the program contains the following directive, the
std::cout is included:



#define DEBUG       /* Turn debugging on */ 


If the program contains the following directive, the
std::cout is omitted:



#undef DEBUG        /* Turn debugging off */ 


Strictly speaking, the #undef DEBUG is
unnecessary. If there is no #define DEBUG
statement, DEBUG is undefined. The #undef
DEBUG
statement is used to indicate explicitly to anyone
reading the code that DEBUG is used for
conditional compilation and is now turned off.



The directive #ifndef causes the
code to be compiled if the symbol is not
defined:



#ifndef STACK_SIZE /* Is stack size defined? */
#define STACK_SIZE 100 /* It's not defined, so define it here */
#endif /* STACK_SIZE */


#else reverses the sense of the
conditional. For example:



#ifdef DEBUG 
std::cout << "Test version. Debugging is on\n";
#else /* DEBUG */
std::cout << "Production version\n";
#endif /* DEBUG */


A programmer may wish to temporarily remove a section of code. A
common method of doing this is to comment out the code by enclosing
it in /* */. This can cause problems, as shown by the following
example:



/***** Comment out this section 
section_report( );
/* Handle the end-of-section stuff */
dump_table( );
**** End of commented out section */


This generates a syntax error for the fifth line. Why? Because the */
on the third line ends the comment that started on the first line,
and the fifth line :



**** End of commented out section */ 


is not a legal C++ statement.



A better method is to use the #ifdef
construct to remove the code.



#ifdef UNDEF 
section_report( );
/* Handle the end-of-section stuff */
dump_table( );
#endif /* UNDEF */


(Of course the code will be included if anyone defines the symbol
UNDEF; however, anyone who does so should be
shot.)



The compiler switch
-Dsymbol allows symbols to be defined on the
command line. For example, the command:



CC -DDEBUG -g -o prog prog.cc 


compiles the program prog.c and includes all the
code in #ifdef DEBUG/#endif /* DEBUG */ pairs,
even though there is no #define DEBUG in the
program. The Borland-C++ equivalent is:



bcc32 -DDEBUG -g -N -eprog.exe prog.c 


The general form of the option is -Dsymbol or
-Dsymbol=value. For
example, the following sets MAX to 10:



CC -DMAX=10 -o prog prog.c 


Most C++ compilers automatically define some system-dependent
symbols. For example, Borland-C++ defines the symbol _
_BORLANDC_ _,
and Windows-based compilers define _
_WIN32
. The ANSI standard compiler C defines the symbol
_ _STDC_ _. C++ compilers define the symbol
_ _cplusplus. Most Unix compilers define a name
for the system (e.g., Sun, VAX, Linux, etc.); however, they are
rarely documented. The symbol unix is always
defined for all Unix machines










Command-line options specify the initial value of a symbol only. Any
#define and #undef directives
in the program can change the symbol's value. For
example, the directive #undef DEBUG results in
DEBUG being undefined whether or not you use
-DDEBUG .










    I l@ve RuBoard



    Chapter 8.&nbsp; Process Control










    Chapter 8. Process Control




      Section 8.1. 
      Introduction


      Section 8.2. 
      Process Identifiers


      Section 8.3. 
      fork Function


      Section 8.4. 
      vfork Function


      Section 8.5. 
      exit Functions


      Section 8.6. 
      wait and waitpid Functions


      Section 8.7. 
      waitid Function


      Section 8.8. 
      wait3 and wait4 Functions


      Section 8.9. 
      Race Conditions


      Section 8.10. 
      exec Functions


      Section 8.11. 
      Changing User IDs and Group IDs


      Section 8.12. 
      Interpreter Files


      Section 8.13. 
      system Function


      Section 8.14. 
      Process Accounting


      Section 8.15. 
      User Identification


      Section 8.16. 
      Process Times


      Section 8.17. 
      Summary

      Exercises








    Section 26.11.&nbsp; Data Areas










    26.11. Data Areas


    Applications often need to read or store data. Depending on the use case, this data may be stored in one of many locations. Consider preferences as an example.


    Typical products use at least some preferences. The preferences themselves may or may not be defined in the product's plug-ins. For example, if you are reusing plug-ins from different products, it is more convenient to manage the preferences outside the plug-in.


    In addition, applications often allow users to change preference values or use preferences to store recently opened files, recent chat partners, and so on. These values might be stored uniquely for each user or shared among users. In scenarios where applications operate on distinct datasets, some of the preferences may even relate to the particular data and should be stored or associated with that data.


    Preferences are just one example, but they illustrate the various scopes and lifecycles that applications have for the data they read and write. Eclipse defines four data areas that capture these characteristics and allows application writers to properly control the scope of their data:


    Install The install area is where Eclipse itself is installed. The install area is generally read-only. The data in the install area is available to all instances of all configurations of Eclipse running on the install. See also Platform.getInstallLocation() and osgi.install.area.

    Configuration The configuration area is where the running configuration of Eclipse is defined. Configuration areas are generally writable. The data in a configuration area is available to all instances of the configuration. Chapter 25, "The Last Mile," contains additional detail on the configuration area. See also Platform.getConfigurationLocation() and osgi.configuration.area.

    Instance The instance area is the default location for user-defined data (e.g., a workspace). The instance area is typically writable. Applications may allow multiple sessions to have concurrent access to the instance area, but must take care to prevent lost updates, etc. See also Platform.getInstanceLocation() and osgi.instance.area.

    Note



    The Eclipse IDE's workspace is an example of the use of instance locations. The Resources plug-in implementers chose to make the default location for projects be in the instance area defined by the Runtime. Eclipse IDE users commonly think they are setting the Resource's workspace location, but actually they are setting the Runtime's instance location.



    User The user area is where Eclipse manages data specific to a user, but independent of the configuration or instance. The user area is typically based on the Java user.home system property and the initial value of the osgi.user.area system property. See also Platform.getUserLocation() and osgi.user.area.


    In addition to these Eclipse wide areas, the Runtime defines two locations specifically for each installed plug-in:


    State location This is a location within the instance area's metadata. See Plugin.getStateLocation().

    Data location This is a location within the configuration's metadata. See Bundle.getDataFile().


    Each of these locations is controlled by setting the system properties described before Eclipse starts (e.g., in the config.ini). Locations are URLs. For simplicity, file paths are also accepted and automatically converted to file: URLs. For better control and convenience, there are also a number of predefined symbolic locations that can be used. Note that not all combinations of location type and symbolic value are valid. Table 26-1 details which combinations are possible.


    Table 26-1. Location Compatibilities

    Location/Value

    Supports default?

    File/URL

    @none

    @noDefault

    @user.home

    @user.dir

    Install

    No

    Yes

    No

    No

    Yes

    Yes

    Configuration

    Yes

    Yes

    Yes[*]

    Yes[*]

    Yes

    Yes

    Instance

    Yes

    Yes

    Yes

    Yes

    Yes

    Yes (default)

    User

    Yes

    Yes

    Yes

    Yes

    Yes

    Yes


    [*] Indicates that this setup is technically possible, but pragmatically quite difficult to manage. In particular, without a configuration location, the Eclipse Runtime may only get as far as starting the OSGi framework.


    @none Indicates that the corresponding location should never be set either explicitly or to its default value. For example, an RCP-style application that has no instance data may use osgi.instance.area=@none to prevent extraneous files being written to disk. @none must not be followed by any path segments.

    @noDefault Forces a location to be undefined or explicitly defined (i.e., Eclipse does not automatically compute a default value). This is useful when you want to allow for data in the corresponding location, but the Eclipse default value is not appropriate. @noDefault must not be followed by any path segments.

    @user.home Directs Eclipse to compute a location value relative to the user's home directory. @user.home can be followed by path segments. In all cases, the string "@user.home" is replaced with the value of the Java user.home system property. For example, setting

    osgi.instance.area=@user.home/myWorkspace

    results in a value of

    file:/users/fred/myWorkspace

    @user.dir Directs Eclipse to compute a location value relative to the current working directory. @user.dir can be followed by path segments. In all cases, the string "@user.dir" is replaced with the value of the Java user.dir system property. For example, setting

    osgi.instance.area=@user.dir/myWorkspace

    results in a value of

    file:/usr/local/eclipse/myWorkspace


    Since the default case is for all locations to be set, valid, and writable, some plug-ins may fail in other setups, even if they are listed as possible. For example, it is unreasonable to expect a plug-in focused on instance data, such as the Resources plug-in, to do much if the instance area is not defined. It is up to plug-in developers to choose the setups they support and design their functions accordingly.


    Note that each of the locations can be statically marked as read-only by setting the corresponding property osgi.AAA.area.readonly=true, where "AAA" is one of the area names.












    Recipe&nbsp;11.11.&nbsp;Configuring Actions to Require SSL










    Recipe 11.11. Configuring Actions to Require SSL




    Problem



    You want to control if HTTPS is required on a page-by-page basis.





    Solution



    Use the SSLEXT Struts extension.





    Discussion



    The Struts SSL Extension (SSLEXT), an open source Struts plug-in,
    enables you to indicate if an action requires the secure
    (https) protocol. Steve Ditlinger created and
    maintains this project (with others), hosted at
    http://sslext.sourceforge.net.



    SSLEXT enables fine-grained secure protocol control by
    providing:



    • The ability to specify in the struts-config.xml file
      if an action should require a secure protocol.
      This feature essentially allows your application to switch actions
      and JSP pages from http to
      https.

    • Extensions of the Struts JSP tags that generate URLs that include the
      https protocol.


    The SSLEXT distribution consists of a plug-in class for
    initialization (SecurePlugIn), a custom request
    processor (SecureRequestProcessor), and a custom
    action mapping class (SecureActionMapping).





    If you have been using custom RequestProcessor or
    ActionMapping classes and you want to use SSLEXT,
    you will need to change these classes to extend the corresponding
    classes provided by SSLEXT.





    For JSP pages, SSLEXT provides custom extensions of Struts tags for
    generating protocol-specific URLs. A custom JSP allows you to
    indicate if a JSP page requires https. SSLEXT
    depends on the Java Secure Socket Extension (JSSE). JSSE is included
    with JDK 1.4 or later. If you're using an older JDK,
    you can download JSSE from Sun's Java site. Finally,
    you'll need to enable SSL for your application
    server. For Tomcat, this can be found in the Tomcat SSL
    How-To
    documentation.



    SSLEXT works by intercepting the request in its
    SecureRequestProcessor. If the request is directed
    toward an action that is marked as secure, the
    SecureRequestProcessor will generate a redirect.
    The redirect will change the protocol to https and
    the port to a secure port (e.g., 443 or 8443). Switching protocols
    sounds simple; however, a request in a Struts application usually
    contains request attributes, and these attributes are lost on a
    redirect. SSLEXT solves this problem by temporarily storing the
    request attributes in the session.



    You can download the SSLEXT distribution from the project web site.
    SSLEXT doesn't include a lot of documentation, but
    it comes with sample applications that demonstrate its use and
    features. If all your requests go through Struts actions, you can
    apply SSLEXT without modifying any Java code or JSP pages.
    Here's how you would apply SSLEXT to a Struts
    application:



    1. Copy the sslext.jar file into your
      application's WEB-INF/lib
      folder.

    2. If you need to use the custom JSP tags, copy the sslext.tld
      file into the WEB-INF/lib folder.


    Make the following changes to the
    struts-config.xml file:



    1. Add the type attribute to the
      action-mappings element to specify the custom
      secure action mapping class:

      <action-mappings type="org.apache.struts.config.SecureActionConfig">


    2. Add the controller element for the secure request
      processor:

      <controller processorClass="org.apache.struts.action.SecureRequestProcessor" />


    3. Add the plug-in declaration to load the SSLEXT
      code:

      <plug-in className="org.apache.struts.action.SecurePlugIn">
      <set-property property="httpPort" value="80"/>
      <set-property property="httpsPort" value="443"/>
      <set-property property="enable" value="true"/>
      <set-property property="addSession" value="true"/>
      </plug-in>


    4. Set the secure property to TRue
      for any action you want to be accessed using
      https:

      <action    path="/reg/Main"
      type="com.oreilly.strutsckbk.ch11.ssl.MainMenuAction">
      <!-- Force this action to run secured -->
      <set-property property="secure" value="true"/>
      <forward name="success" path="/reg/main.jsp"/>
      </action>


    5. Set the secure property to
      false for any action that you
      only want to run under an unsecured protocol
      (http):

      <action    path="/Welcome"
      type="com.oreilly.strutsckbk.ch11.ssl.WelcomeAction">
      <!-- Force this action to run unsecured -->
      <set-property property="secure" value="false"/>
      <forward name="success" path="/welcome.jsp"/>
      </action>



    If you have accessible JSP pages you want to specify as secured (or
    unsecured), use the SSLEXT pageScheme custom JSP
    tag:



    <%@ taglib uri="http://www.ebuilt.com/taglib" prefix="sslext"%>
    <sslext:pageScheme secure="true"/>




    Now rebuild and deploy the application. When you click on a link to a
    secured action, the protocol will switch to
    https and the port to the
    secure port (e.g., 8443 or
    443). If you go to an action
    marked as unsecured, the protocol and port should switch back to
    http and the port to the standard port (e.g.,
    8080 or
    80). If you access an action without a specified
    value for the secure property or the value is set to
    any, then the protocol won't
    switch when you access the action. If you're under
    http, the protocol will remain
    http; if you're under
    https, the protocol will remain
    https.





    Be careful if you switch from a secured to unsecured protocol (https
    to http). Critical user-specific data, such as the current session
    ID, can be snooped by a hacker. The hacker could use this data to
    hijack the session and imposter the user. Here is a good rule to
    follow: Once you switch to https, stay in https.





    You can use SSLEXT alongside container-managed
    security mechanisms for specifying secure transport. The
    container-managed security approach works well when you want to
    secure entire portions of your application:



    <security-constraint>
    <web-resource-collection>
    <web-resource-name>AdminPages</web-resource-name>
    <description>Administrative pages</description>
    <url-pattern>/admin/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
    <role-name>jscAdmin</role-name>
    </auth-constraint>
    <!-- Switch to HTTPS for the admin pages -->
    <user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
    </security-constraint>




    You can then use SSLEXT for fine-grained control of the protocol at
    the action level.





    See Also



    Enabling an application server to support https
    varies. Tomcat provides a how-to for this. For Tomcat 5.0, the
    relevant documentation can be found at http://jakarta.apache.org/tomcat/tomcat-5.0-doc/ssl-howto.html.



    SSLEXT is hosted on SourceForge at http://sslext.sourceforge.net.



    Craig McClanahan presents a good argument against switching back to
    http from https. His comments
    can be found in a struts-user mailing list
    thread archived at http://www.mail-archive.com/struts-user@jakarta.apache.org/msg81889.html.



    Recipe 11.9 shows how you can specify the
    protocol in the web.xml file. This approach,
    presented as part of the J2EE tutorial, can be found at http://java.sun.com/j2ee/1.4/docs/tutorial/doc/Security4.html.












      Section 3.5. Prototype







      3.5. Prototype

      Every object is linked to a prototype object from which it can inherit properties. All objects created from object literals are linked to Object.prototype, an object that comes standard with JavaScript.

      When you make a new object, you can select the object that should be its prototype. The mechanism that JavaScript provides to do this is messy and complex, but it can be significantly simplified. We will add a beget method to the Object function. The beget method creates a new object that uses an old object as its prototype. There will be much more about functions in the next chapter.

      if (typeof Object.beget !== 'function') {
      Object.beget = function (o) {
      var F = function () {};
      F.prototype = o;
      return new F();
      };
      }
      var another_stooge = Object.beget(stooge);


      The prototype link has no effect on updating. When we make changes to an object, the object's prototype is not touched:

      another_stooge['first-name'] = 'Harry';
      another_stooge['middle-name'] = 'Moses';
      another_stooge.nickname = 'Moe';


      The prototype link is used only in retrieval. If we try to retrieve a property value from an object, and if the object lacks the property name, then JavaScript attempts to retrieve the property value from the prototype object. And if that object is lacking the property, then it goes to its prototype, and so on until the process finally bottoms out with Object.prototype. If the desired property exists nowhere in the prototype chain, then the result is the undefined value. This is called delegation.

      The prototype relationship is a dynamic relationship. If we add a new property to a prototype, that property will immediately be visible in all of the objects that are based on that prototype:

      stooge.profession = 'actor';
      another_stooge.profession // 'actor'


      We will see more about the prototype chain in Chapter 6.








      22.10 ''Virtual'' methods



      [ Team LiB ]










      22.10 Virtual methods


      The keyword virtual
      in front of a parent class method tells the C++ compiler that the class has a child class which has a method which has the same name but which acts differently in the child class. If (a) a method is declared as virtual, and (b) the object which calls the method is referred to via a pointer, then (c) the compiled program will, even while it is running, be able to decide which implementation of the virtual
      method to use. It is worth stressing that this 'runtime binding' only works if you the programmer fulfill both conditions: (a) you use virtual
      in your method declaration, and (b) you use a pointer to your object.


      Except in the case of a destructor, corresponding virtual functions have the same name. You don't put the word virtual
      in front of the actual function implementation code in the *.cpp. You can put virtual
      in front of the child class function declaration in the child class's header file or not, as you like. In other words, to start with, you really only need to put virtual
      in one place: in front of the parent class's declaration of the function.


      But, in order to make our code more readable, when we derive off child classes from a parent class with a virtual function, we usually do put virtual
      in front of the child method declaration as well as in the parent method declaration. The child does need to have a declaration for the method in order to override it in any case.


      One slightly weird thing is that a parent class destructor like ~cProgrammer()
      needs to be declared virtual
      even though a child class destructor like ~cTeacherProgrammer()
      seems to have a different name. But you don't in fact call the destructor by name. In a program where we have a cProgrammer *_ptextbookauthor, we might be calling either the cProgrammer
      or the cTeacher
      destructor with a line like delete _ptextbookauthor;. The thing is, it's possible that _ptextbookauthor got initialized as new cTeacher, so we just don't know.


      The delete
      operator calls the destructor without referring to the destructor method by name. So the compiled code needs to actually look at the type of the _textbookauthor pointer to find out whether it's really a cProgrammer
      * or a cTeacherProgrammer*, so it knows which destructor to use. And unless you fulfilled the 'virtual condition' by making the destructor virtual, then the code won't know to do runtime binding and choose between using the parent or the child method as appropriate. The destructors are different here because the cTeacherProgrammer
      has more stuff to destroy, in particular, the cGollywog *_pimaginaryfriend.



      Slogan for a class: If your child is richer than you, you need a virtual destructor.



      One final point should be mentioned here. Ordinarily, when you have a method virtual void somemethod()
      in a base class called, say, cParentClass, then when you override the method in a child class called, say, cChildClass, the child class somemethod()
      will call the parent class method if we explicitly ask it to with code like this.



      void cChildClass::somemethod()
      {
      cParentClass::somemethod();
      //Your extra childclass code goes here....
      }

      But in the case of a virtual
      destructor, the parent class's destructor method will be automatically called when the object is deleted. This is in accord with the standard C++ execution order of constructors and destructors mentioned in the last subsection.



      cChildClass::~cChildClass()
      {
      //Your extra child class destructor code goes here...
      ...
      /* The cParentClass::~cParentClass destructor will be
      automatically called
      here at the end of the ~cChildClass destructor call. */
      }





        [ Team LiB ]