--- /dev/null
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
--- /dev/null
+# Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The full GNU General Public License is included in this distribution in
+# the file called "COPYING".
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=bmx6-luci
+PKG_RELEASE:=2
+
+PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/bmx6-luci
+ SECTION:=luci
+ CATEGORY:=LuCI
+ SUBMENU:=3. Applications
+ TITLE:= Bmx6 configuration, status and visualization module
+# DEPENDS:=+bmx6 +bmx6-uci-config
+ DEPENDS:=+luci-lib-json +luci-mod-admin-core +luci-lib-httpclient
+endef
+
+define Package/bmx6-luci/description
+ bmx6 web module for LuCi web interface
+endef
+
+define Package/bmx6-luci/conffiles
+ /etc/config/luci-bmx6
+endef
+
+define Build/Prepare
+ mkdir -p $(PKG_BUILD_DIR)
+endef
+
+define Build/Configure
+endef
+
+define Build/Compile
+endef
+
+define Package/bmx6-luci/install
+ $(CP) ./files/* $(1)/
+ chmod 755 $(1)/www/cgi-bin/bmx6-info
+endef
+
+$(eval $(call BuildPackage,bmx6-luci))
+
--- /dev/null
+config 'bmx6' 'luci'
+ option ignore '0'
+ #option place 'admin status Bmx6'
+ option place 'qmp Mesh'
+ option position '3'
+ #option json 'http://127.0.0.1/cgi-bin/bmx6-info?'
+ option json 'exec:/www/cgi-bin/bmx6-info -s'
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+ Contributors Jo-Philipp Wich <xm@subsignal.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+local bmx6json = require("luci.model.bmx6json")
+
+module("luci.controller.bmx6", package.seeall)
+
+function index()
+ local place = {}
+ local ucim = require "luci.model.uci"
+ local uci = ucim.cursor()
+
+ -- checking if ignore is on
+ if uci:get("luci-bmx6","luci","ignore") == "1" then
+ return nil
+ end
+
+ -- getting value from uci database
+ local uci_place = uci:get("luci-bmx6","luci","place")
+
+ -- default values
+ if uci_place == nil then
+ place = {"bmx6"}
+ else
+ local util = require "luci.util"
+ place = util.split(uci_place," ")
+ end
+
+ -- getting position of menu
+ local uci_position = uci:get("luci-bmx6","luci","position")
+
+ ---------------------------
+ -- Starting with the pages
+ ---------------------------
+
+ --- status (default)
+ entry(place,call("action_nodes_j"),place[#place],tonumber(uci_position))
+
+ table.insert(place,"Status")
+ entry(place,call("action_status_j"),"Status",0)
+ table.remove(place)
+
+ -- not visible
+ table.insert(place,"nodes_nojs")
+ entry(place, call("action_nodes"), nil)
+ table.remove(place)
+
+ --- nodes
+ table.insert(place,"Nodes")
+ entry(place,call("action_nodes_j"),"Nodes",1)
+ table.remove(place)
+
+ --- links
+ table.insert(place,"Links")
+ entry(place,call("action_links"),"Links",2).leaf = true
+ table.remove(place)
+
+ -- Tunnels
+ table.insert(place,"Tunnels")
+ entry(place,call("action_tunnels_j"), "Tunnels", 3).leaf = true
+ table.remove(place)
+
+ -- Gateways (deprecated)
+ --table.insert(place,"Gateways")
+ --entry(place,call("action_gateways_j"),"Gateways").leaf = true
+ --table.remove(place)
+
+ --- Chat
+ table.insert(place,"Chat")
+ entry(place,call("action_chat"),"Chat",5)
+ table.remove(place)
+
+ --- Graph
+ table.insert(place,"Graph")
+ entry(place, template("bmx6/graph"), "Graph",4)
+ table.remove(place)
+
+ --- Topology (hidden)
+ table.insert(place,"topology")
+ entry(place, call("action_topology"), nil)
+ table.remove(place)
+
+ --- configuration (CBI)
+ table.insert(place,"Configuration")
+ entry(place, cbi("bmx6/main"), "Configuration",6).dependent=false
+
+ table.insert(place,"General")
+ entry(place, cbi("bmx6/main"), "General",1)
+ table.remove(place)
+
+ table.insert(place,"Advanced")
+ entry(place, cbi("bmx6/advanced"), "Advanced",5)
+ table.remove(place)
+
+ table.insert(place,"Interfaces")
+ entry(place, cbi("bmx6/interfaces"), "Interfaces",2)
+ table.remove(place)
+
+ table.insert(place,"Tunnels")
+ entry(place, cbi("bmx6/tunnels"), "Tunnels",3)
+ table.remove(place)
+
+ table.insert(place,"Plugins")
+ entry(place, cbi("bmx6/plugins"), "Plugins",6)
+ table.remove(place)
+
+ table.insert(place,"HNAv6")
+ entry(place, cbi("bmx6/hna"), "HNAv6",4)
+ table.remove(place)
+
+ table.remove(place)
+
+end
+
+function action_status()
+ local status = bmx6json.get("status").status or nil
+ local interfaces = bmx6json.get("interfaces").interfaces or nil
+
+ if status == nil or interfaces == nil then
+ luci.template.render("bmx6/error", {txt="Cannot fetch data from bmx6 json"})
+ else
+ luci.template.render("bmx6/status", {status=status,interfaces=interfaces})
+ end
+end
+
+function action_status_j()
+ luci.template.render("bmx6/status_j", {})
+end
+
+
+function action_nodes()
+ local orig_list = bmx6json.get("originators").originators or nil
+
+ if orig_list == nil then
+ luci.template.render("bmx6/error", {txt="Cannot fetch data from bmx6 json"})
+ return nil
+ end
+
+ local originators = {}
+ local desc = nil
+ local orig = nil
+ local name = ""
+ local ipv4 = ""
+
+ for _,o in ipairs(orig_list) do
+ orig = bmx6json.get("originators/"..o.name) or {}
+ desc = bmx6json.get("descriptions/"..o.name) or {}
+
+ if string.find(o.name,'.') then
+ name = luci.util.split(o.name,'.')[1]
+ else
+ name = o.name
+ end
+
+ table.insert(originators,{name=name,orig=orig,desc=desc})
+ end
+
+ luci.template.render("bmx6/nodes", {originators=originators})
+end
+
+function action_nodes_j()
+ local http = require "luci.http"
+ local link_non_js = "/cgi-bin/luci" .. http.getenv("PATH_INFO") .. '/nodes_nojs'
+
+ luci.template.render("bmx6/nodes_j", {link_non_js=link_non_js})
+end
+
+function action_gateways_j()
+ luci.template.render("bmx6/gateways_j", {})
+end
+
+function action_tunnels_j()
+ luci.template.render("bmx6/tunnels_j", {})
+end
+
+
+function action_links(host)
+ local links = bmx6json.get("links", host)
+ local devlinks = {}
+ local _,l
+
+ if links ~= nil then
+ links = links.links
+ for _,l in ipairs(links) do
+ devlinks[l.viaDev] = {}
+ end
+ for _,l in ipairs(links) do
+ l.globalId = luci.util.split(l.globalId,'.')[1]
+ table.insert(devlinks[l.viaDev],l)
+ end
+ end
+
+ luci.template.render("bmx6/links", {links=devlinks})
+end
+
+function action_topology()
+ local originators = bmx6json.get("originators/all")
+ local o,i,l,i2
+ local first = true
+ local topology = '[ '
+ local cache = '/tmp/bmx6-topology.json'
+ local offset = 60
+
+ local cachefd = io.open(cache,r)
+ local update = false
+
+ if cachefd ~= nil then
+ local lastupdate = tonumber(cachefd:read("*line")) or 0
+ if os.time() >= lastupdate + offset then
+ update = true
+ else
+ topology = cachefd:read("*all")
+ end
+ cachefd:close()
+ end
+
+ if cachefd == nil or update then
+ for i,o in ipairs(originators) do
+ local links = bmx6json.get("links",o.primaryIp)
+ if links then
+ if first then
+ first = false
+ else
+ topology = topology .. ', '
+ end
+
+ topology = topology .. '{ "globalId": "%s", "links": [' %o.globalId:match("^[^%.]+")
+
+ local first2 = true
+
+ for i2,l in ipairs(links.links) do
+ if first2 then
+ first2 = false
+ else
+ topology = topology .. ', '
+ end
+
+ topology = topology .. '{ "globalId": "%s", "rxRate": %s, "txRate": %s }'
+ %{ l.globalId:match("^[^%.]+"), l.rxRate, l.txRate }
+
+ end
+
+ topology = topology .. ']}'
+ end
+
+ end
+
+ topology = topology .. ' ]'
+
+ -- Upgrading the content of the cache file
+ cachefd = io.open(cache,'w+')
+ cachefd:write(os.time()..'\n')
+ cachefd:write(topology)
+ cachefd:close()
+ end
+
+ luci.http.prepare_content("application/json")
+ luci.http.write(topology)
+end
+
+
+function action_chat()
+ local sms_dir = "/var/run/bmx6/sms"
+ local rcvd_dir = sms_dir .. "/rcvdSms"
+ local send_file = sms_dir .. "/sendSms/chat"
+ local sms_list = bmx6json.get("rcvdSms")
+ local sender = ""
+ local sms_file = ""
+ local chat = {}
+ local to_send = nil
+ local sent = ""
+ local fd = nil
+
+ if luci.sys.call("test -d " .. sms_dir) ~= 0 then
+ luci.template.render("bmx6/error", {txt="sms plugin disabled or some problem with directory " .. sms_dir})
+ return nil
+ end
+
+ sms_list = luci.util.split(luci.util.exec("ls "..rcvd_dir.."/*:chat"))
+
+ for _,sms_path in ipairs(sms_list) do
+ if #sms_path > #rcvd_dir then
+ sms_file = luci.util.split(sms_path,'/')
+ sms_file = sms_file[#sms_file]
+ sender = luci.util.split(sms_file,':')[1]
+
+ -- Trying to clean the name
+ if string.find(sender,".") ~= nil then
+ sender = luci.util.split(sender,".")[1]
+ end
+
+ fd = io.open(sms_path,"r")
+ chat[sender] = fd:read()
+ fd:close()
+ end
+ end
+
+ to_send = luci.http.formvalue("toSend")
+ if to_send ~= nil and #to_send > 1 then
+ fd = io.open(send_file,"w")
+ fd:write(to_send)
+ fd:close()
+ sent = to_send
+ else
+ sent = luci.util.exec("cat "..send_file)
+ end
+
+ luci.template.render("bmx6/chat", {chat=chat,sent=sent})
+end
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+ Contributors Jo-Philipp Wich <xm@subsignal.org>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+local ltn12 = require("luci.ltn12")
+local json = require("luci.json")
+local util = require("luci.util")
+local uci = require("luci.model.uci")
+local sys = require("luci.sys")
+local template = require("luci.template")
+local http = require("luci.http")
+local string = require("string")
+local table = require("table")
+local nixio = require("nixio")
+local nixiofs = require("nixio.fs")
+local ipairs = ipairs
+
+module "luci.model.bmx6json"
+
+-- Returns a LUA object from bmx6 JSON daemon
+
+function get(field, host)
+ local url
+ if host ~= nil then
+ if host:match(":") then
+ url = 'http://[%s]/cgi-bin/bmx6-info?' % host
+ else
+ url = 'http://%s/cgi-bin/bmx6-info?' % host
+ end
+ else
+ url = uci.cursor():get("luci-bmx6","luci","json")
+ end
+
+ if url == nil then
+ print_error("bmx6 json url not configured, cannot fetch bmx6 daemon data",true)
+ return nil
+ end
+
+ local json_url = util.split(url,":")
+ local raw = ""
+
+ if json_url[1] == "http" then
+ raw,err = wget(url..field,1000)
+ else
+
+ if json_url[1] == "exec" then
+ raw = sys.exec(json_url[2]..' '..field)
+ else
+ print_error("bmx6 json url not recognized, cannot fetch bmx6 daemon data. Use http: or exec:",true)
+ return nil
+ end
+
+ end
+
+ local data = nil
+
+ if raw and raw:len() > 10 then
+ local decoder = json.Decoder()
+ ltn12.pump.all(ltn12.source.string(raw), decoder:sink())
+ data = decoder:get()
+-- else
+-- print_error("Cannot get data from bmx6 daemon",true)
+-- return nil
+ end
+
+ return data
+end
+
+function print_error(txt,popup)
+ util.perror(txt)
+ sys.call("logger -t bmx6json " .. txt)
+
+ if popup then
+ http.write('<script type="text/javascript">alert("Some error detected, please check it: '..txt..'");</script>')
+ else
+ http.write("<h1>Dammit! some error detected</h1>")
+ http.write("bmx6-luci: " .. txt)
+ http.write('<p><FORM><INPUT TYPE="BUTTON" VALUE="Go Back" ONCLICK="history.go(-1)"></FORM></p>')
+ end
+
+end
+
+function text2html(txt)
+ txt = string.gsub(txt,"<","{")
+ txt = string.gsub(txt,">","}")
+ txt = util.striptags(txt)
+ return txt
+end
+
+
+function wget(url, timeout)
+ local rfd, wfd = nixio.pipe()
+ local pid = nixio.fork()
+ if pid == 0 then
+ rfd:close()
+ nixio.dup(wfd, nixio.stdout)
+
+ local candidates = { "/usr/bin/wget", "/bin/wget" }
+ local _, bin
+ for _, bin in ipairs(candidates) do
+ if nixiofs.access(bin, "x") then
+ nixio.exec(bin, "-q", "-O", "-", url)
+ end
+ end
+ return
+ else
+ wfd:close()
+ rfd:setblocking(false)
+
+ local buffer = { }
+ local err1, err2
+
+ while true do
+ local ready = nixio.poll({{ fd = rfd, events = nixio.poll_flags("in") }}, timeout)
+ if not ready then
+ nixio.kill(pid, nixio.const.SIGKILL)
+ err1 = "timeout"
+ break
+ end
+
+ local rv = rfd:read(4096)
+ if rv then
+ -- eof
+ if #rv == 0 then
+ break
+ end
+
+ buffer[#buffer+1] = rv
+ else
+ -- error
+ if nixio.errno() ~= nixio.const.EAGAIN and
+ nixio.errno() ~= nixio.const.EWOULDBLOCK then
+ err1 = "error"
+ err2 = nixio.errno()
+ end
+ end
+ end
+
+ nixio.waitpid(pid, "nohang")
+ if not err1 then
+ return table.concat(buffer)
+ else
+ return nil, err1, err2
+ end
+ end
+end
+
+function getOptions(name)
+ -- Getting json and Checking if bmx6-json is avaiable
+ local options = get("options")
+ if options == nil or options.OPTIONS == nil then
+ m.message = "bmx6-json plugin is not running or some mistake in luci-bmx6 configuration, check /etc/config/luci-bmx6"
+ return nil
+ else
+ options = options.OPTIONS
+ end
+
+ -- Filtering by the option name
+ local i,_
+ local namedopt = nil
+ if name ~= nil then
+ for _,i in ipairs(options) do
+ if i.name == name and i.CHILD_OPTIONS ~= nil then
+ namedopt = i.CHILD_OPTIONS
+ break
+ end
+ end
+ end
+
+ return namedopt
+end
+
+-- Rturns a help string formated to be used in HTML scope
+function getHtmlHelp(opt)
+ if opt == nil then return nil end
+
+ local help = ""
+ if opt.help ~= nil then
+ help = text2html(opt.help)
+ end
+ if opt.syntax ~= nil then
+ help = help .. "<br/><b>Syntax: </b>" .. text2html(opt.syntax)
+ end
+
+ return help
+end
+
+function testandreload()
+ local test = sys.call('bmx6 -c --test > /tmp/bmx6-luci.err.tmp')
+ if test ~= 0 then
+ return sys.exec("cat /tmp/bmx6-luci.err.tmp")
+ end
+
+ local err = sys.call('bmx6 -c --configReload > /tmp/bmx6-luci.err.tmp')
+ if err ~= 0 then
+ return sys.exec("cat /tmp/bmx6-luci.err.tmp")
+ end
+
+ return nil
+end
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+m = Map("bmx6", "bmx6")
+
+local bmx6json = require("luci.model.bmx6json")
+local util = require("luci.util")
+local http = require("luci.http")
+local sys = require("luci.sys")
+
+local options = bmx6json.get("options")
+if options == nil or options.OPTIONS == nil then
+ m.message = "bmx6-json plugin is not running or some mistake in luci-bmx6 configuration, check /etc/config/luci-bmx6"
+ options = {}
+else
+ options = options.OPTIONS
+end
+
+local general = m:section(NamedSection,"general","general","General Options")
+
+local name = ""
+local help = ""
+local value = nil
+local _,o
+
+for _,o in ipairs(options) do
+ if o.name ~= nil and o.CHILD_OPTIONS == nil and o.configurable == 1 then
+ help = ""
+ name = o.name
+
+ if o.help ~= nil then
+ help = bmx6json.text2html(o.help)
+ end
+
+ if o.syntax ~= nil then
+ help = help .. "<br/><strong>Syntax: </strong>" .. bmx6json.text2html(o.syntax)
+ end
+
+ if o.def ~= nil then
+ help = help .. "<strong> Default: </strong>" .. o.def
+ end
+
+ value = general:option(Value,name,name,help)
+
+ end
+end
+
+function m.on_commit(self,map)
+ local err = sys.call('bmx6 -c --configReload > /tmp/bmx6-luci.err.tmp')
+ if err ~= 0 then
+ m.message = sys.exec("cat /tmp/bmx6-luci.err.tmp")
+ end
+end
+
+return m
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+local sys = require("luci.sys")
+
+m = Map("bmx6", "bmx6")
+
+local hna = m:section(TypedSection,"unicastHna","IPv6 HNA")
+hna.addremove = true
+hna.anonymous = true
+local hna_option = hna:option(Value,"unicastHna", "IPv6 Host Network Announcement. Syntax <NETADDR>/<PREFIX>")
+
+--function hna_option:validate(value)
+-- local err = sys.call('bmx6 -c --test -a ' .. value)
+-- if err ~= 0 then
+-- return nil
+-- end
+-- return value
+--end
+
+function m.on_commit(self,map)
+ local err = sys.call('bmx6 -c --configReload > /tmp/bmx6-luci.err.tmp')
+ if err ~= 0 then
+ m.message = sys.exec("cat /tmp/bmx6-luci.err.tmp")
+ end
+end
+
+return m
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+local sys = require("luci.sys")
+local bmx6json = require("luci.model.bmx6json")
+local m = Map("bmx6", "bmx6")
+
+local eth_int = sys.net.devices()
+local interfaces = m:section(TypedSection,"dev","Devices","")
+interfaces.addremove = true
+interfaces.anonymous = true
+
+local intlv = interfaces:option(ListValue,"dev","Device")
+
+for _,i in ipairs(eth_int) do
+ intlv:value(i,i)
+end
+
+-- Getting json and looking for device section
+local json = bmx6json.get("options")
+
+if json == nil or json.OPTIONS == nil then
+ m.message = "bmx6-json plugin is not running or some mistake in luci-bmx6 configuration, check /etc/config/luci-bmx6"
+ json = {}
+else
+ json = json.OPTIONS
+end
+
+local dev = {}
+for _,j in ipairs(json) do
+ if j.name == "dev" and j.CHILD_OPTIONS ~= nil then
+ dev = j.CHILD_OPTIONS
+ break
+ end
+end
+
+local help = ""
+local name = ""
+
+for _,o in ipairs(dev) do
+ if o.name ~= nil then
+ help = ""
+ name = o.name
+ if o.help ~= nil then
+ help = bmx6json.text2html(o.help)
+ end
+
+ if o.syntax ~= nil then
+ help = help .. "<br/><strong>Syntax: </strong>" .. bmx6json.text2html(o.syntax)
+ end
+
+ value = interfaces:option(Value,name,name,help)
+ value.optional = true
+ end
+end
+
+
+return m
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+local sys = require("luci.sys")
+local bmx6json = require("luci.model.bmx6json")
+
+m = Map("bmx6", "bmx6")
+
+-- Getting json and Checking if bmx6-json is avaiable
+local options = bmx6json.get("options")
+if options == nil or options.OPTIONS == nil then
+ m.message = "bmx6-json plugin is not running or some mistake in luci-bmx6 configuration, check /etc/config/luci-bmx6"
+else
+ options = options.OPTIONS
+end
+
+-- Getting a list of interfaces
+local eth_int = luci.sys.net.devices()
+
+-- Getting the most important options from general
+local general = m:section(NamedSection,"general","general","General")
+general.addremove = false
+general:option(Value,"globalPrefix","Global ip prefix","Specify global prefix for interfaces: NETADDR/LENGTH. If you are using IPv6 leave blank to let bmx6 autoassign an ULA IPv6 address.")
+
+if m:get("ipVersion","ipVersion") == "6" then
+ general:option(Value,"tun4Address","IPv4 address or range","specify default IPv4 tunnel address and announced range")
+end
+
+-- IP section
+-- ipVersion section is important, we are allways showing it
+local ipV = m:section(NamedSection,"ipVersion","ipVersion","IP options")
+ipV.addremove = false
+local lipv = ipV:option(ListValue,"ipVersion","IP version")
+lipv:value("4","4")
+lipv:value("6","6")
+lipv.default = "6"
+
+-- rest of ip options are optional, getting them from json
+local ipoptions = {}
+for _,o in ipairs(options) do
+ if o.name == "ipVersion" and o.CHILD_OPTIONS ~= nil then
+ ipoptions = o.CHILD_OPTIONS
+ break
+ end
+end
+
+local help = ""
+local name = ""
+local value = nil
+
+for _,o in ipairs(ipoptions) do
+ if o.name ~= nil then
+ help = ""
+ name = o.name
+ if o.help ~= nil then
+ help = bmx6json.text2html(o.help)
+ end
+
+ if o.syntax ~= nil then
+ help = help .. "<br/><strong>Syntax: </strong>" .. bmx6json.text2html(o.syntax)
+ end
+
+ if o.def ~= nil then
+ help = help .. "<br/><strong> Default: </strong>" .. bmx6json.text2html(o.def)
+ end
+
+ value = ipV:option(Value,name,name,help)
+ value.optional = true
+ end
+end
+
+-- Interfaces section
+local interfaces = m:section(TypedSection,"dev","Devices","")
+interfaces.addremove = true
+interfaces.anonymous = true
+local intlv = interfaces:option(ListValue,"dev","Device")
+
+for _,i in ipairs(eth_int) do
+ intlv:value(i,i)
+end
+
+function m.on_commit(self,map)
+ local err = sys.call('bmx6 -c --configReload > /tmp/bmx6-luci.err.tmp')
+ if err ~= 0 then
+ m.message = sys.exec("cat /tmp/bmx6-luci.err.tmp")
+ end
+end
+
+return m
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+local sys = require("luci.sys")
+
+m = Map("bmx6", "bmx6")
+plugins_dir = {"/usr/lib/","/var/lib","/lib"}
+
+plugin = m:section(TypedSection,"plugin","Plugin")
+plugin.addremove = true
+plugin.anonymous = true
+plv = plugin:option(ListValue,"plugin", "Plugin")
+
+for _,d in ipairs(plugins_dir) do
+ pl = luci.sys.exec("cd "..d..";ls bmx6_*")
+ if #pl > 6 then
+ for _,v in ipairs(luci.util.split(pl,"\n")) do
+ plv:value(v,v)
+ end
+ end
+end
+
+
+function m.on_commit(self,map)
+ local err = sys.call('/etc/init.d/bmx6 restart')
+ if err ~= 0 then
+ m.message = sys.exec("Cannot restart bmx6")
+ end
+end
+
+
+return m
+
--- /dev/null
+--[[
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+--]]
+
+local sys = require("luci.sys")
+local bmx6json = require("luci.model.bmx6json")
+
+m = Map("bmx6", "bmx6")
+
+-- tunOut
+local tunnelsOut = m:section(TypedSection,"tunOut",translate("Networks to fetch"),translate("Tunnel announcements to fetch if possible"))
+tunnelsOut.addremove = true
+tunnelsOut.anonymous = true
+tunnelsOut:option(Value,"tunOut","Name")
+tunnelsOut:option(Value,"network", translate("Network to fetch"))
+
+local tunoptions = bmx6json.getOptions("tunOut")
+local _,o
+for _,o in ipairs(tunoptions) do
+ if o.name ~= nil and o.name ~= "network" then
+ help = bmx6json.getHtmlHelp(o)
+ value = tunnelsOut:option(Value,o.name,o.name,help)
+ value.optional = true
+ end
+end
+
+
+--tunIn
+local tunnelsIn = m:section(TypedSection,"tunInNet",translate("Networks to offer"),translate("Tunnels to announce in the network"))
+tunnelsIn.addremove = true
+tunnelsIn.anonymous = true
+
+local net = tunnelsIn:option(Value,"tunInNet", translate("Network to offer"))
+net.default = "10.0.0.0/8"
+
+local bwd = tunnelsIn:option(Value,"bandwidth",translate("Bandwidth (Bytes)"))
+bwd.default = "1000000"
+
+local tuninoptions = bmx6json.getOptions("tunInNet")
+local _,o
+for _,o in ipairs(tuninoptions) do
+ if o.name ~= nil and o.name ~= "tunInNet" and o.name ~= "bandwidth" then
+ help = bmx6json.getHtmlHelp(o)
+ value = tunnelsIn:option(Value,o.name,o.name,help)
+ value.optional = true
+ end
+end
+
+function m.on_commit(self,map)
+ --Not working. If test returns error the changes are still commited
+ local msg = bmx6json.testandreload()
+ if msg ~= nil then
+ m.message = msg
+ end
+end
+
+return m
+
--- /dev/null
+<script type="text/javascript">//<![CDATA[
+
+ XHR.poll(5, '/cgi-bin/bmx6-info', { '$neighbours': '' },
+ function(x, st)
+ {
+ var originators = st.neighbours[0].originators;
+ var descriptions = st.neighbours[1].descriptions;
+
+ var tb = document.getElementById('descriptions_table');
+
+ if ( originators.length != descriptions.length )
+ {
+ var tr = tb.insertRow(-1);
+ tr.className = 'cbi-section-table-row';
+ var td = tr.insertCell(-1);
+ td.colSpan = 7;
+ td.innerHTML = '<em><br /><%:Some problem with JSON: lenght of originators and descriptions different. %></em>';
+ return 1;
+ }
+
+ if ( originators && descriptions && tb)
+ {
+ /* clear all rows */
+ while( tb.rows.length > 1 )
+ tb.deleteRow(1);
+
+ for( var i = 0; i < descriptions.length; i++ )
+ {
+ var tr = tb.insertRow(-1);
+ tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
+ tr.insertCell(-1).innerHTML = descriptions[i].DESC_ADV.globalId.replace(/\.[^\.]+$/,"");
+
+ var extensions = descriptions[i].DESC_ADV.extensions;
+
+ //Looking for the extensions
+ var hna6 = [];
+ for( var e = 0; e < extensions.length; e++)
+ {
+ if( extensions[e].HNA6_EXTENSION )
+ {
+ hna6 = extensions[e].HNA6_EXTENSION;
+ break;
+ }
+ }
+
+ //Adding first HNA with prefix=128 as main address
+ var ipstxt = '';
+ var address;
+ var prefix;
+
+ for( var e = 0; e < hna6.length; e++ )
+ {
+ address = hna6[e].address;
+ prefix = hna6[e].prefixlen;
+ if ( prefix == '128' )
+ {
+ ipstxt += address;
+ break;
+ }
+ }
+
+ tr.insertCell(-1).innerHTML = ipstxt;
+
+ tr.insertCell(-1).innerHTML = originators[i].viaDev;
+ tr.insertCell(-1).innerHTML = originators[i].metric;
+ tr.insertCell(-1).innerHTML = originators[i].lastDesc;
+ tr.insertCell(-1).innerHTML = originators[i].lastRef;
+ tr.insertCell(-1).innerHTML = originators[i].blocked;
+
+ }
+
+ if( tb.rows.length == 1 )
+ {
+ var tr = tb.insertRow(-1);
+ tr.className = 'cbi-section-table-row';
+
+ var td = tr.insertCell(-1);
+ td.colSpan = 7;
+ td.innerHTML = '<em><br /><%:There are no nodes available.%></em>';
+ }
+ }
+ }
+ );
+//]]></script>
+
+<div class="cbi-map">
+
+<fieldset class="cbi-section">
+ <legend><%:Mesh nodes%></legend>
+ <table class="cbi-section-table" id="descriptions_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"><%:Hostname%></th>
+ <th class="cbi-section-table-cell"><%:Primary IP%></th>
+ <th class="cbi-section-table-cell"><%:Via Device%></th>
+ <th class="cbi-section-table-cell"><%:Metric%></th>
+ <th class="cbi-section-table-cell"><%:Last Desc%></th>
+ <th class="cbi-section-table-cell"><%:Last Ref%></th>
+ <th class="cbi-section-table-cell"><%:Blocked%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="7"><em><br /><%:Collecting data...%></em></td>
+ </tr>
+ </table>
+</fieldset>
+
+</div>
+
+
--- /dev/null
+<%+header%>
+<meta http-equiv="refresh" content="60" />
+<h2><a id="content" name="content"><%:Chat%></a></h2>
+<p>This is sms a chat where all bmx6 nodes can participate. The data is replayed using routing packets, so there is a limit of 2040 bytes. Use it only to send short messages.</p>
+<p>Each participant can only send one sms at same time.</p>
+<br />
+
+<strong>Received SMS</strong>
+<br />
+<pre style="background-color:#dadbe6;">
+<% for orig,sms in pairs(chat) do %>
+ <u><%=orig%></u>:<%=sms%>
+<% end %>
+</pre>
+
+<form action=".">
+ <input type="submit" value="refresh" />
+</form>
+
+<br />
+
+<form action="." method="post">
+ <input type="text" name="toSend" />
+ <input type="submit" value="send sms" />
+</form>
+
+<br />
+<table>
+ <tr>
+ <td><strong>Your last sms: </strong></td>
+ <td><pre><%=sent%></pre></td>
+ </tr>
+</table>
+<%+footer%>
+
--- /dev/null
+<%+header%>
+<h2><a id="content" name="content"><%:ERROR%></a></h2>
+<strong>Some error has occurred</strong>
+<br />
+<pre>
+ <%=txt%>
+</pre>
+<br />
+<%+footer%>
+
--- /dev/null
+<%+header%>
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+
+<script type="text/javascript">//<![CDATA[
+
+ var displayExtraInfo = function ( id ) {
+
+ document.getElementById('extra-info').innerHTML = document.getElementById(id).innerHTML;
+ }
+ XHR.poll(5, '/cgi-bin/bmx6-info', { 'descriptions/all': '' },
+ function(x, st)
+ {
+ var tb = document.getElementById('descriptions_table');
+ var rowcount = 0;
+ var tunicon = "<%=resource%>/icons/tunnel.png";
+ /* clear all rows */
+ while( tb.rows.length > 1 ) tb.deleteRow(1);
+
+ for ( var k in st )
+ {
+ var description = st[k].DESC_ADV;
+ var tun4in6;
+
+ for ( var k in description.extensions )
+ {
+ var value = description.extensions[k];
+
+ if ( value.TUN4IN6_NET_EXTENSION )
+ {
+ tun4in6 = value.TUN4IN6_NET_EXTENSION;
+ break;
+ }
+ }
+
+ if ( tun4in6 )
+ {
+ var nodename = description.globalId.replace(/\..+$/,'');
+
+ for( var i = 0; i < tun4in6.length; i++ )
+ {
+ var tr = tb.insertRow(-1);
+ var network = tun4in6[i].network;
+ var network_len = tun4in6[i].networklen;
+ var network_bw = tun4in6[i].bandwidth;
+
+ if ( network_len >= 32 ) continue;
+
+ tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((rowcount++ % 2) + 1);
+ tr.insertCell(-1).innerHTML = String.format('<a href="/cgi-bin/bmx6control?function=gwselect&node=%s"><img src="%s" /></a>',nodename,tunicon);
+ tr.insertCell(-1).innerHTML = nodename;
+ tr.insertCell(-1).innerHTML = network + '/' + network_len;
+ tr.insertCell(-1).innerHTML = network_bw;
+
+
+ }
+
+ if( tb.rows.length == 1 )
+ {
+ var tr = tb.insertRow(-1);
+ tr.className = 'cbi-section-table-row';
+
+ var td = tr.insertCell(-1);
+ td.colSpan = 4;
+ td.innerHTML = '<em><br /><%:There are no gateways announced in the network.%></em>';
+ }
+ }
+ }
+ }
+ );
+//]]></script>
+
+<style>
+
+ div.hideme{
+ display: none;
+ }
+
+ div.info{
+ background: #FFF;
+ border: solid 1px;
+ height: 80px;
+ display: block;
+ overflow: auto;
+ }
+
+ div.inforow{
+ text-align:left;
+ display:inline-block;
+ width:20%;
+ margin:5px;
+ vertical-align:top;
+
+ }
+
+#extra-info ul { list-style: none outside none; margin-left: 0em; }
+
+</style>
+<div class="cbi-map">
+
+<h2>Originators</h2>
+<div class="cbi-map-descr"></div>
+<fieldset class="cbi-section">
+ <legend><%:Mesh gateways%></legend>
+ <table class="cbi-section-table" id="descriptions_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"></th>
+ <th class="cbi-section-table-cell"><%:Node%></th>
+ <th class="cbi-section-table-cell"><%:Network%></th>
+ <th class="cbi-section-table-cell"><%:Bandwidth%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="4"><em><br /><%:Collecting data...%></em></td>
+ </tr>
+ </table>
+</fieldset>
+
+</div>
+
+<%+footer%>
+
--- /dev/null
+<%#
+Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+Contributors Jo-Philip
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with this program; if not, write to the Free Software Foundation, Inc.,
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+The full GNU General Public License is included in this distribution in
+the file called "COPYING".
+-%>
+
+<%
+ luci.http.prepare_content("text/html")
+
+ local location = { unpack(luci.dispatcher.context.path) }
+ location[#location] = "topology"
+%>
+
+<%+header%>
+
+<script type="text/javascript" src="<%=resource%>/bmx6/js/raphael-min.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/dracula_graffle.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/jquery-1.4.2.min.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/dracula_graph.js"></script>
+
+<button id="redraw" onclick="redraw();">  redraw  </button>
+
+<div id="wait" style="text-align: center">
+<br /><br />
+<img src="<%=resource%>/icons/loading.gif" />
+<%:Collecting data...%>
+
+</div>
+
+<div id="canvas" style="min-width:800px; min-height:800px"></div>
+
+<script type="text/javascript">//<![CDATA[
+ var redraw;
+
+ XHR.get('<%=luci.dispatcher.build_url(unpack(location))%>', null,
+ function(x, data)
+ {
+ var g = new Graph();
+ var seen = { };
+
+ for (var i = 0; i < (data.length); i++)
+ {
+ // node->node
+ if (data[i].globalId)
+ {
+ for (var j = 0; j < (data[i].links.length); j++)
+ {
+ var key = (data[i].globalId < data[i].links[j].globalId)
+ ? data[i].globalId + '|' + data[i].links[j].globalId
+ : data[i].links[j].globalId + '|' + data[i].globalId;
+
+ var rxRate = data[i].links[j].rxRate;
+ var txRate = data[i].links[j].txRate;
+
+ if (!seen[key] && rxRate>0 && txRate>0)
+ {
+ g.addEdge(data[i].globalId, data[i].links[j].globalId,
+ { label: rxRate + '/' + txRate,
+ directed: false, stroke: '#aaaaaa', fill: '#ffffff',
+ 'label-style': { 'font-size': 8 }});
+ seen[key] = true;
+ }
+ }
+ }
+ //g.addEdge(data[i].router, data[i].neighbor,
+ // { label: data[i].label, directed: true, stroke: '#aaaaaa' });
+ // node->leaf
+ //else if (data[i].router && data[i].gateway)
+ // g.addEdge(data[i].router, data[i].gateway,
+ // { label: 'leaf', stroke: '#cccccc' });
+ }
+
+ var canvas = document.getElementById('canvas');
+
+ var layouter = new Graph.Layout.Spring(g);
+ layouter.layout();
+
+ var divwait = document.getElementById("wait");
+ divwait.parentNode.removeChild(divwait);
+
+ var renderer = new Graph.Renderer.Raphael(canvas.id, g, canvas.offsetWidth, canvas.offsetHeight);
+ renderer.draw();
+
+ redraw = function() {
+ layouter.layout();
+ renderer.draw();
+ }
+
+ }
+ );
+//]]></script>
+
+
+<%+footer%>
--- /dev/null
+<%+header%>
+
+ <style type="text/css">
+ table.int {
+ border-width: 2px;
+ border-spacing: ;
+ border-style: inset;
+ border-color: white;
+ border-collapse: collapse;
+ background-color: #dadbe6;
+ }
+ table.int tr {
+ border-width: 5px;
+ padding: 4px;
+ border-style: solid;
+ border-color: white;
+ background-color: #dadbe9;
+ }
+ table.int td {
+ border-width: 5px;
+ padding: 4px;
+ border-style: solid;
+ border-color: white;
+ background-color: #dadbe9;
+ text-align: center;
+ }
+ </style>
+
+<h2><a id="content" name="content"><%:Interfaces%></a></h2>
+Interfaces where bmx6 is running
+<br />
+<br />
+<table class="int">
+<tr>
+ <td><strong>Name</strong></td>
+ <td><strong>State</strong></td>
+ <td><strong>Type</strong></td>
+ <td><strong>Rate (Min/Max)</strong></td>
+ <td><strong>Local IP</strong></td>
+ <td><strong>Global IP</strong></td>
+ <td><strong>Multicast IP</strong></td>
+ <td><strong>Primary</strong></td>
+</tr>
+<% for i,v in ipairs(data) do %>
+ <tr>
+ <td><%=v.devName%></td>
+ <td><%=v.state%></td>
+ <td><%=v.type%></td>
+ <td><%=v.rateMin%>/<%=v.rateMax%></td>
+ <td><%=v.llocalIp%></td>
+ <td><%=v.globalIp%></td>
+ <td><%=v.multicastIp%></td>
+ <td><%=v.primary%></td>
+ </tr>
+<%end%>
+</table>
+
+<br />
+<%+footer%>
--- /dev/null
+<%+header%>
+<meta http-equiv="refresh" content="10" />
+<h2><a id="content" name="content"><%:Links%></a></h2>
+<br />
+<div id="links" style="position:relative;padding-left:2px">
+
+<% for j,d in pairs(links) do %>
+
+ <table>
+ <tr>
+ <td><img src="/luci-static/resources/bmx6/wifi.png"/></td>
+ <td><strong><%=j%></strong></td>
+ </tr>
+ </table>
+
+ <div style="display:table">
+
+<% for i,l in ipairs(d) do %>
+ <div id="link" style="background-color:#dadbe9;left:50px;width:300px;margin:10px;float:left;position:relative">
+ <table>
+ <tr><th colspan="2">
+ <span style="color:grey;font-weight:700;text-align:left;">
+ <%=l.globalId%>
+ <br />
+ </span>
+ </th></tr>
+ <tr>
+ <td><img src="/luci-static/resources/bmx6/link.png"/></td>
+ <td>
+ <ul>
+ <li>Local IP: <a href="[<%=l.llocalIp%>]"><%=l.llocalIp%></a></li>
+ <li>Device: <%=l.viaDev%></li>
+ <li>Rate (rx/tx):
+ <% if l.rxRate+l.txRate < 120 then %>
+ <span style="color:red;">
+ <% else %>
+ <span style="color:green;">
+ <% end %>
+ <%=l.rxRate%>/<%=l.txRate%>
+ </span>
+ </li>
+ <li>Routes: <%=l.routes%></li>
+ </ul>
+ </td>
+ </tr>
+ </table>
+ </div>
+<% end %>
+</div>
+<% end %>
+</div>
+
+<br />
+<br />
+<%+footer%>
--- /dev/null
+<%+header%>
+<style type="text/css">
+
+ table {
+ width:90%;
+ border-top:1px solid #e5eaf8;
+ border-right:1px solid #e5eaf8;
+ margin:1em auto;
+ border-collapse:collapse;
+ }
+
+ td {
+ color:#678197;
+ border-bottom:1px solid #e6eff8;
+ border-left:1px solid #e6eff8;
+ padding:.3em 1em;
+ text-align:center;
+ }
+ th {
+ background:#f4f9fe;
+ text-align:center;
+ font:bold 1.2em/2em "Century Gothic","Trebuchet MS",Arial,Helvetica,sans-serif;
+ color:#66a3d3;
+ }
+
+
+#neighbour {
+ position:relative;
+ margin:5px;
+}
+
+#orig_id {
+ background-color: black;
+ color: white;
+ text-ident:10px;
+}
+</style>
+
+<h2><a id="content" name="content"><%:Neighbours%></a></h2>
+<table>
+ <tr>
+ <th scope="col">Name</th>
+ <th scope="col">IPv4</th>
+ <th scope="col">IPv6</th>
+ <th scope="col">Via Dev</th>
+ <th scope="col">Via IP</th>
+ <th scope="col">Routes</th>
+ <th scope="col">Metric</th>
+ <th scope="col">Last Desc</th>
+ <th scope="col">Last Ref</th>
+ </tr>
+
+<% for i,o in ipairs(originators) do %>
+ <tr>
+ <td><%=o.name%></td>
+ <td><a href="http://<%=o.ipv4%>"><%=o.ipv4%></a></td>
+ <td><a href="http://[<%=o.orig.primaryIp%>]"><%=o.orig.primaryIp%></a></td>
+ <td><%=o.orig.viaDev%></td>
+ <td><%=o.orig.viaIp%></td>
+ <td><%=o.orig.routes%></td>
+ <td><%=o.orig.metric%></td>
+ <td><%=o.orig.lastDesc%></td>
+ <td><%=o.orig.lastRef%></td>
+ </tr>
+<%end%>
+</table>
+
+<table>
+ <tr>
+ <th scope="col">Node</th>
+ <th scope="col">Announced networks</th>
+ </tr>
+
+<% for i,o in ipairs(originators) do %>
+ <tr>
+ <td><%=o.name%></td>
+ <td style="text-align:left;">
+ <% if o.desc.DESC_ADV ~= nil then %>
+ <% for j,h in ipairs(o.desc.DESC_ADV.extensions[2].HNA6_EXTENSION) do %>
+ <%=h.address%>
+ <% end %>
+ <% end %>
+ </td>
+ </tr>
+<% end %>
+</table>
+
+<br />
+<%+footer%>
--- /dev/null
+<%+header%>
+<script type="text/javascript">//<![CDATA[
+
+ var displayExtraInfo = function ( id ) {
+
+ document.getElementById('extra-info').innerHTML = document.getElementById(id).innerHTML;
+ }
+ XHR.poll(5, '/cgi-bin/bmx6-info', { '$neighbours': '' },
+ function(x, st)
+ {
+ var originators = st.neighbours[0].originators;
+ var descriptions = st.neighbours[1].descriptions;
+
+ var tb = document.getElementById('descriptions_table');
+
+ if ( originators.length != descriptions.length )
+ {
+ var tr = tb.insertRow(-1);
+ tr.className = 'cbi-section-table-row';
+ var td = tr.insertCell(-1);
+ td.colSpan = 7;
+ td.innerHTML = '<em><br /><%:Some problem with JSON: lenght of originators and descriptions different. %></em>';
+ td.innerHTML += '<em><%: Please perform a manually cache flush from a terminal: bmx6 -c --flushAll %></em>'
+ return 1;
+ }
+
+ if ( originators && descriptions && tb)
+ {
+ /* clear all rows */
+ while( tb.rows.length > 1 )
+ tb.deleteRow(1);
+
+ for( var i = 0; i < descriptions.length; i++ )
+ {
+ var tr = tb.insertRow(-1);
+ var nodename = descriptions[i].DESC_ADV.globalId.replace(/\.[^\.]+$/,"");
+ tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
+ tr.insertCell(-1).innerHTML = '<a onclick="displayExtraInfo(\'ip-'+i+'\')"><img src=\"<%=resource%>/cbi/help.gif\" /></a>';
+ tr.insertCell(-1).innerHTML = nodename;
+
+ var extensions = descriptions[i].DESC_ADV.extensions;
+
+ //Looking for the extensions
+ var hna6 = [];
+ var tun4in6 = [];
+ var tun6 = [];
+ for( var e = 0; e < extensions.length; e++)
+ {
+ if( extensions[e].HNA6_EXTENSION )
+ {
+ hna6 = extensions[e].HNA6_EXTENSION;
+ }
+ if ( extensions[e].TUN4IN6_NET_EXTENSION )
+ {
+ tun4in6 = extensions[e].TUN4IN6_NET_EXTENSION;
+ }
+ }
+
+ var gateways = '<ul>';
+ for ( var t = 0; t < tun4in6.length; t++)
+ {
+ if ( tun4in6[t].networklen == "32" )
+ gateways += '<li><a href="http://' + tun4in6[t].network + '">' + tun4in6[t].network + '</a></li>';
+ else
+ gateways += "<li>"+tun4in6[t].network+'/'+tun4in6[t].networklen + ' | ' + tun4in6[t].bandwidth+'</li>';
+ }
+ gateways += '</ul>';
+
+ //Adding HNAs with prefix=128 as main address
+ var ipstxt = '';
+ var address;
+ var first = 1;
+ var ipstxt_hidden = '<ul>';
+ var hna6list = '<ul>';
+
+ for( var e = 0; e < hna6.length; e++ )
+ {
+ address = hna6[e].address;
+ prefix = hna6[e].prefixlen;
+ if ( prefix == '128' )
+ {
+ if (first)
+ {
+ ipstxt += address;
+ ipstxt_hidden += '<li><a href="http://['+address+']" >'+address+"</a></li>";
+ first = 0;
+ }
+ else {
+ ipstxt_hidden += '<li><a href="http://['+address+']" >'+address+"</a></li>";
+ }
+ }
+ else {
+ hna6list += '<li>'+address+'/'+prefix+'</li>';
+ }
+ }
+ hna6list += '</ul>';
+ ipstxt_hidden += '</ul>';
+
+ tr.insertCell(-1).innerHTML = ipstxt;
+
+ tr.insertCell(-1).innerHTML = originators[i].viaDev;
+ tr.insertCell(-1).innerHTML = originators[i].metric;
+ tr.insertCell(-1).innerHTML = originators[i].lastDesc;
+ tr.insertCell(-1).innerHTML = originators[i].lastRef;
+ tr.insertCell(-1).innerHTML = originators[i].blocked;
+ //tr.onclick = displayExtraInfo("ip-"+i);
+
+ extrainfo = '<div id="ip-'+ i +'" class="hideme">' + "<div class='inforow'><br /><br /><h4>" + nodename + '</h4></div>\n' + "<div class='inforow'><h5>Available IPs</h5>\n<p>" + ipstxt_hidden + "</p></div>\n" + "<div class='inforow'><h5>Gateways announced</h5>\n<p>" + gateways + "</p></div>\n" + "<div class='inforow'><h5>Networks announced</h5>\n<p>" + hna6list + "</p></div>\n";
+
+ extrainfo += "\n</div>";
+
+ tr.insertCell(-1).innerHTML = extrainfo;
+
+ }
+
+ if( tb.rows.length == 1 )
+ {
+ var tr = tb.insertRow(-1);
+ tr.className = 'cbi-section-table-row';
+
+ var td = tr.insertCell(-1);
+ td.colSpan = 7;
+ td.innerHTML = '<em><br /><%:There are no nodes available.%></em>';
+ }
+ }
+ }
+ );
+//]]></script>
+
+<style>
+
+ div.hideme{
+ display: none;
+ }
+
+ div.info{
+ background: #FFF;
+ border: solid 1px;
+ height: 80px;
+ display: block;
+ overflow: auto;
+ text-align: center;
+ }
+
+ div.inforow{
+ text-align:center;
+ display:inline-block;
+ width:20%;
+ margin:5px;
+ vertical-align:top;
+ }
+
+#extra-info ul { list-style: none outside none; margin-left: 0em; }
+
+</style>
+<div class="cbi-map">
+
+<h2>Originators</h2>
+<div class="cbi-map-descr"></div>
+<div id="extra-info" class="info">
+ <br />
+ Click to the icon <img src="<%=resource%>/cbi/help.gif" /> to see individual node information
+</div>
+<fieldset class="cbi-section">
+ <legend><%:Mesh nodes%></legend>
+ <table class="cbi-section-table" id="descriptions_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"></th>
+ <th class="cbi-section-table-cell"><%:Hostname%></th>
+ <th class="cbi-section-table-cell"><%:Primary IP%></th>
+ <th class="cbi-section-table-cell"><%:Via Device%></th>
+ <th class="cbi-section-table-cell"><%:Metric%></th>
+ <th class="cbi-section-table-cell"><%:Last Desc%></th>
+ <th class="cbi-section-table-cell"><%:Last Ref%></th>
+ <th class="cbi-section-table-cell"><%:Blocked%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="8"><em><br /><%:Collecting data...%></em></td>
+ </tr>
+ </table>
+</fieldset>
+
+</div>
+<a href="<%=link_non_js%>">Go to non JavaScript view</a>
+
+
+<%+footer%>
+
--- /dev/null
+<%+header%>
+<style type="text/css">
+
+ table {
+ width:90%;
+ border-top:1px solid #e5eaf8;
+ border-right:1px solid #e5eaf8;
+ margin:1em auto;
+ border-collapse:collapse;
+ }
+
+ td {
+ color:#678197;
+ border-bottom:1px solid #e6eff8;
+ border-left:1px solid #e6eff8;
+ padding:.3em 1em;
+ text-align:center;
+ }
+ th {
+ background:#f4f9fe;
+ text-align:center;
+ font:bold 1.2em/2em "Century Gothic","Trebuchet MS",Arial,Helvetica,sans-serif;
+ color:#66a3d3;
+ }
+
+
+#neighbour {
+ position:relative;
+ margin:5px;
+}
+
+#orig_id {
+ background-color: black;
+ color: white;
+ text-ident:10px;
+}
+</style>
+
+<h2><a id="content" name="content"><%:Nodes%></a></h2>
+<table>
+ <tr>
+ <th scope="col">Name</th>
+ <th scope="col">IPv6</th>
+ <th scope="col">Via Dev</th>
+ <th scope="col">Via IP</th>
+ <th scope="col">Routes</th>
+ <th scope="col">Metric</th>
+ <th scope="col">Last Desc</th>
+ <th scope="col">Last Ref</th>
+ </tr>
+
+<% for i,o in ipairs(originators) do %>
+ <tr>
+ <td><%=o.name%></td>
+ <td><a href="http://[<%=o.orig.primaryIp%>]"><%=o.orig.primaryIp%></a></td>
+ <td><%=o.orig.viaDev%></td>
+ <td><%=o.orig.viaIp%></td>
+ <td><%=o.orig.routes%></td>
+ <td><%=o.orig.metric%></td>
+ <td><%=o.orig.lastDesc%></td>
+ <td><%=o.orig.lastRef%></td>
+ </tr>
+<%end%>
+</table>
+
+<table>
+ <tr>
+ <th scope="col">Node</th>
+ <th scope="col">Announced networks</th>
+ </tr>
+
+<% for i,o in ipairs(originators) do %>
+ <tr>
+ <td><%=o.name%></td>
+ <td style="text-align:left;">
+ <% if o.desc.DESC_ADV ~= nil then %>
+ <% for j,h in ipairs(o.desc.DESC_ADV.extensions[2].HNA6_EXTENSION) do %>
+ <%=h.address%>
+ <% end %>
+ <% end %>
+ </td>
+ </tr>
+<% end %>
+</table>
+
+<br />
+<%+footer%>
--- /dev/null
+<%#
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+ Contributors Lluis Esquerda <eskerda@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+-%>
+
+<%+header%>
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/polling.js"></script>
+
+
+<style>
+
+ div.hideme{
+ display: none;
+ }
+
+ div.info{
+ background: #FFF;
+ border: solid 0px;
+ height: 90px;
+ display: block;
+ overflow: auto;
+ }
+
+ div.inforow{
+ text-align:left;
+ display:inline-block;
+ width:20%;
+ margin:5px;
+ vertical-align:top;
+
+ }
+
+#extra-info ul { list-style: none outside none; margin-left: 0em; }
+
+</style>
+<div class="cbi-map">
+
+<h2>Node originators</h2>
+<div class="cbi-map-descr"></div>
+<div id="extra-info" class="info">
+ <br />
+ <center>
+ Click icon <img src="<%=resource%>/bmx6/world.png" /> to see individual node information
+ </center>
+</div>
+<fieldset class="cbi-section">
+ <legend><%:Mesh nodes%></legend>
+ <table class="cbi-section-table" id="descriptions_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"></th>
+ <th class="cbi-section-table-cell"><%:Hostname%></th>
+ <th class="cbi-section-table-cell"><%:Primary IP%></th>
+ <th class="cbi-section-table-cell"><%:Via Device%></th>
+ <th class="cbi-section-table-cell"><%:Metric%></th>
+ <th class="cbi-section-table-cell"><%:Last Desc%></th>
+ <th class="cbi-section-table-cell"><%:Last Ref%></th>
+ <th class="cbi-section-table-cell"><%:Blocked%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="8"><br /><center><em><%:Collecting data...%></em></center></td>
+ </tr>
+ </table>
+</fieldset>
+
+</div>
+
+<a href="<%=link_non_js%>">Go to non JavaScript view</a>
+
+<script type="text/javascript">//<![CDATA[
+ var displayExtraInfo = function ( id ) {
+ document.getElementById('extra-info').innerHTML = document.getElementById(id).innerHTML;
+ }
+
+ new TablePooler(5,"/cgi-bin/bmx6-info", {'$neighbours':''}, "descriptions_table", function(st){
+ var infoicon = "<%=resource%>/bmx6/world_small.png";
+ var nodeicon = "<%=resource%>/bmx6/world.png";
+ var originators = st.neighbours[0].originators;
+ var descriptions = st.neighbours[1].descriptions;
+ var res = Array();
+ var error = "";
+
+ if ( originators.length != descriptions.length )
+ {
+ error = '<em><br /><%:Some problem with JSON: lenght of originators and descriptions differs. %> \
+ <%: Please perform a cache flush from a console it this persists: bmx6 -c --flushAll %></em>';
+ res.push([[error,7]]);
+ return res;
+ }
+
+ for ( var i = 0; i < descriptions.length; i++ ){
+ var nodename = descriptions[i].DESC_ADV.globalId.replace(/\.[^\.]+$/,"");
+ var extensions = descriptions[i].DESC_ADV.extensions;
+ //var extrainfo = '<a onclick="displayExtraInfo(\'ip-' + i + '\')"><img src="' + infoicon + '" / ></a>';
+ var extrainfo_link = '<a onclick="displayExtraInfo(\'ip-' + i + '\')">' + '<img src="' + infoicon + '" />' + '</a>';
+ // Looking for the extensions
+ var hna6 = [];
+ var tun4in6 = [];
+ var tun6 = [];
+ for( var e = 0; e < extensions.length; e++)
+ {
+ if( extensions[e].HNA6_EXTENSION )
+ hna6 = extensions[e].HNA6_EXTENSION;
+ if ( extensions[e].TUN4IN6_NET_EXTENSION )
+ tun4in6 = extensions[e].TUN4IN6_NET_EXTENSION;
+ }
+
+ // Gateways
+ var gateways = '<ul>';
+ for ( var t = 0; t < tun4in6.length; t++)
+ {
+ if ( tun4in6[t].networklen == "32" )
+ gateways += '<li><a href="http://' + tun4in6[t].network + '">' + tun4in6[t].network + '</a></li>';
+ else
+ gateways += "<li>"+tun4in6[t].network+'/'+tun4in6[t].networklen + ' | ' + tun4in6[t].bandwidth+'</li>';
+ }
+ gateways += '</ul>';
+
+ //Adding HNAs with prefix=128 as main address
+ var ipstxt = '';
+ var address;
+ var first = 1;
+ var ipstxt_hidden = '<ul>';
+ var hna6list = '<ul>';
+ var extrainfo = "";
+
+ for( var e = 0; e < hna6.length; e++ )
+ {
+ address = hna6[e].address;
+ prefix = hna6[e].prefixlen;
+ if ( prefix == '128' )
+ {
+ if (first)
+ {
+ ipstxt += address;
+ ipstxt_hidden += '<li><a href="http://['+address+']" >'+address+"</a></li>";
+ first = 0;
+ }
+ else {
+ ipstxt_hidden += '<li><a href="http://['+address+']" >'+address+"</a></li>";
+ }
+ }
+ else {
+ hna6list += '<li>'+address+'/'+prefix+'</li>';
+ }
+ }
+ hna6list += '</ul>';
+ ipstxt_hidden += '</ul>';
+
+ extrainfo = '<div id="ip-'+ i +'" class="hideme">'
+ + "<div class='inforow'>"
+ + "<h4>" + nodename + '</h4>\n' + '<img src="' + nodeicon + '" />'+ "</div>"
+
+ + "<div class='inforow'>"
+ + "<h5>Available IPs</h5>\n"
+ + ipstxt_hidden + "</div>\n"
+
+ + "<div class='inforow'>"
+ + "<h5>Gateways announced</h5>\n"
+ + gateways + "</div>\n"
+
+ + "<div class='inforow'>"
+ + "<h5>Networks announced</h5>\n"
+ + hna6list + "</div>\n"
+ + "\n</div>";
+
+ res.push([extrainfo_link,nodename, ipstxt, originators[i].viaDev, originators[i].metric,
+ originators[i].lastDesc, originators[i].lastRef, originators[i].blocked, extrainfo]);
+
+ }
+ return res;
+ });
+//]]></script>
+
+<%+footer%>
+
--- /dev/null
+<%+header%>
+<link rel="stylesheet" type="text/css" href="/luci-static/resources/bmx6/style.css" />
+
+<img src="<%=resource%>/bmx6/bmx6logo.png" />
+
+Bmx6 is a routing protocol for Linux based operating systems. Visit <a href="http://bmx6.net">bmx6.net</a> for more info.
+
+<br />
+<br />
+
+<h2>Status of bmx6</h2>
+
+<table>
+<tr>
+ <th>Version</th>
+ <th>Compatibility</th>
+ <th>CodeVersion</th>
+ <th>Global Id</th>
+ <th>Primary Ip</th>
+ <th>Local Id</th>
+ <th>Uptime</th>
+ <th>CPU</th>
+ <th>Nodes</th>
+</tr>
+ <tr>
+ <td><%=status.version%></td>
+ <td><%=status.compatibility%></td>
+ <td><%=status.codeVersion%></td>
+ <td><%=status.globalId%>/<%=status.rateMax%></td>
+ <td><%=status.primaryIp%></td>
+ <td><%=status.myLocalId%></td>
+ <td><%=status.uptime%></td>
+ <td><%=status.cpu%></td>
+ <td><%=status.nodes%></td>
+ </tr>
+</table>
+
+<br />
+<br />
+
+<h2>Status of interfaces</h2>
+<table>
+<tr>
+ <th>Name</th>
+ <th>State</th>
+ <th>Type</th>
+ <th>Rate (Min/Max)</th>
+ <th>Local IP</th>
+ <th>Global IP</th>
+ <th>Multicast IP</th>
+ <th>Primary</th>
+</tr>
+<% for i,v in ipairs(interfaces) do %>
+ <tr>
+ <td><%=v.devName%></td>
+ <td><%=v.state%></td>
+ <td><%=v.type%></td>
+ <td><%=v.rateMin%>/<%=v.rateMax%></td>
+ <td><%=v.llocalIp%></td>
+ <td><%=v.globalIp%></td>
+ <td><%=v.multicastIp%></td>
+ <td><%=v.primary%></td>
+ </tr>
+<%end%>
+</table>
+
+<br />
+
+<%+footer%>
--- /dev/null
+<%+header%>
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/polling.js"></script>
+
+
+<style>
+
+ div.hideme{
+ display: none;
+ }
+
+ div.info{
+ background: #FFF;
+ border: solid 1px;
+ height: 80px;
+ display: block;
+ overflow: auto;
+ }
+
+ div.inforow{
+ text-align:left;
+ display:inline-block;
+ width:20%;
+ margin:5px;
+ vertical-align:top;
+
+ }
+
+#extra-info ul { list-style: none outside none; margin-left: 0em; }
+
+</style>
+<div class="cbi-map">
+<center>
+<img src="<%=resource%>/bmx6/bmx6logo.png" />
+<br />
+<br />
+a mesh routing protocol for Linux devices.<br />
+Visit <a href="http://bmx6.net">bmx6.net</a> for more info.
+<br />
+<br />
+</center>
+
+<h2>status</h2>
+<div class="cbi-map-descr"></div>
+<fieldset class="cbi-section">
+ <legend><%:status%></legend>
+ <table class="cbi-section-table" id="status_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"><%:Version%></th>
+ <th class="cbi-section-table-cell"><%:Compat%></th>
+ <th class="cbi-section-table-cell"><%:Code Version%></th>
+ <th class="cbi-section-table-cell"><%:Global ID%></th>
+ <th class="cbi-section-table-cell"><%:Primary IP%></th>
+ <th class="cbi-section-table-cell"><%:Tun6Address%></th>
+ <th class="cbi-section-table-cell"><%:Tun4Address%></th>
+ <th class="cbi-section-table-cell"><%:Local ID%></th>
+ <th class="cbi-section-table-cell"><%:Uptime%></th>
+ <th class="cbi-section-table-cell"><%:Cpu load%></th>
+ <th class="cbi-section-table-cell"><%:Nodes seen%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="11"><em><br /><%:Collecting data...%></em></td>
+ </tr>
+ </table>
+</fieldset>
+
+<fieldset class="cbi-section">
+
+ <legend><%:Network devices%></legend>
+ <table class="cbi-section-table" id="ifaces_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"><%:Name%></th>
+ <th class="cbi-section-table-cell"><%:State%></th>
+ <th class="cbi-section-table-cell"><%:Type%></th>
+ <th class="cbi-section-table-cell"><%:Rate%></th>
+ <th class="cbi-section-table-cell"><%:Local IP%></th>
+ <th class="cbi-section-table-cell"><%:Global IP%></th>
+ <th class="cbi-section-table-cell"><%:Multicast IP%></th>
+ <th class="cbi-section-table-cell"><%:is Primary%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="8"><em><br /><%:Collecting data...%></em></td>
+ </tr>
+ </table>
+</fieldset>
+
+</div>
+
+<script type="text/javascript">//<![CDATA[
+ new TablePooler(5,"/cgi-bin/bmx6-info", {'status':''}, "status_table", function(st){
+ var res = Array();
+ var sta = st.status;
+
+ res.push([sta.version,sta.compat,sta.revision,sta.globalId,sta.primaryIp,sta.tun6Address,
+ sta.tun4Address,sta.myLocalId,sta.uptime,sta.cpu,sta.nodes]);
+ return res;
+ });
+
+ new TablePooler(5,"/cgi-bin/bmx6-info", {'interfaces':''}, "ifaces_table", function(st){
+ var res = Array();
+ var ifaces = st.interfaces;
+
+ for ( var i = 0; i < ifaces.length; i++)
+ {
+ res.push([ifaces[i].devName,ifaces[i].state,ifaces[i].type,ifaces[i].rateMin+"/"+ifaces[i].rateMax,
+ ifaces[i].llocalIp,ifaces[i].globalIp,ifaces[i].multicastIp,ifaces[i].primary]);
+ }
+ return res;
+ });
+//]]></script>
+
+
+<%+footer%>
+
--- /dev/null
+<%#
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+ Contributors Lluis Esquerda <eskerda@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+-%>
+
+
+<%+header%>
+<script type="text/javascript" src="<%=resource%>/cbi.js"></script>
+<script type="text/javascript" src="<%=resource%>/bmx6/js/polling.js"></script>
+
+
+<style>
+
+ div.hideme{
+ display: none;
+ }
+
+ div.info{
+ background: #FFF;
+ border: solid 1px;
+ height: 80px;
+ display: block;
+ overflow: auto;
+ }
+
+ div.inforow{
+ text-align:left;
+ display:inline-block;
+ width:20%;
+ margin:5px;
+ vertical-align:top;
+
+ }
+
+#extra-info ul { list-style: none outside none; margin-left: 0em; }
+
+</style>
+<div class="cbi-map">
+
+<h2>Gateways tunnel announcements</h2>
+<div class="cbi-map-descr"></div>
+<fieldset class="cbi-section">
+ <legend><%:Mesh gateways%></legend>
+ <table class="cbi-section-table" id="descriptions_table">
+ <tr class="cbi-section-table-titles">
+ <th class="cbi-section-table-cell"></th>
+ <th class="cbi-section-table-cell"><%:Tunnel%></th>
+ <th class="cbi-section-table-cell"><%:Node%></th>
+ <th class="cbi-section-table-cell"><%:Network%></th>
+ <th class="cbi-section-table-cell"><%:Bandwidth%></th>
+ <th class="cbi-section-table-cell"><%:SearchNet%></th>
+ <th class="cbi-section-table-cell"><%:Type%></th>
+ <th class="cbi-section-table-cell"><%:Path Metric%></th>
+ <th class="cbi-section-table-cell"><%:IP metric%></th>
+ <th class="cbi-section-table-cell"><%:Tun metric%></th>
+ <th class="cbi-section-table-cell"><%:Bonus%></th>
+ <th class="cbi-section-table-cell"><%:search id%></th>
+ </tr>
+ <tr class="cbi-section-table-row">
+ <td colspan="10"><em><br /><%:Collecting data...%></em></td>
+ </tr>
+ </table>
+</fieldset>
+
+</div>
+
+<script type="text/javascript">//<![CDATA[
+ new TablePooler(5,"/cgi-bin/bmx6-info", {'$tunnels':''}, "descriptions_table", function(st){
+ var tunicon = "<%=resource%>/icons/tunnel.png";
+ var tunicon_dis = "<%=resource%>/icons/tunnel_disabled.png";
+ var applyicon = "<%=resource%>/cbi/apply.gif";
+ var res = Array();
+ for ( var k in st.tunnels ){
+ var tunnel = st.tunnels[k];
+ var nodename = tunnel.remoteId.replace(/\..+$/,'');
+ var advnet = tunnel.advNet;
+ var status = '<img src="'+tunicon_dis+'"/>';
+
+ if ( tunnel.tunName != "---" ) status = '<img src="'+tunicon+'"/>';
+ if ( advnet == "0.0.0.0/0" ) advnet = "<b>Internet</b>";
+
+ res.push([status, tunnel.name, nodename, advnet, tunnel.advBw, tunnel.searchNet, tunnel.advType,
+ tunnel.pathMtc, tunnel.ipMtc, tunnel.tunMtc, tunnel.bonus, tunnel.searchId]);
+ }
+ return res;
+ });
+//]]></script>
+
+<%+footer%>
+
--- /dev/null
+<%+header%>
+<h2><a id="content" name="content"><%:Wireless%></a></h2>
+<br />
+<h3>Wireless</h3>
+<%=data%>
+<br />
+<%+footer%>
--- /dev/null
+#!/bin/sh
+# Copyright (C) 2011 Pau Escrich
+# Contributors Jo-Philipp Wich <xm@subsignal.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The full GNU General Public License is included in this distribution in
+# the file called "COPYING".
+#
+# This script gives information about bmx6
+# Can be executed from a linux shell: ./bmx6-info -s links
+# Or from web interfae (with cgi enabled): http://host/cgi-bin/bmx6-info?links
+# If you ask for a directory you wil get the directory contents in JSON forman
+
+BMX6_DIR="$(uci get bmx6.general.runtimeDir 2>/dev/null)" || BMX6_DIR="/var/run/bmx6/json"
+
+#Checking if shell mode or cgi-bin mode
+if [ "$1" == "-s" ]; then
+ QUERY="$2"
+else
+ QUERY="${QUERY_STRING%%=*}"
+ echo "Content-type: application/json"
+ echo ""
+
+fi
+
+check_path() {
+ [ -d "$1" ] && path=$(cd $1; pwd)
+ [ -f "$1" ] && path=$(cd $1/..; pwd)
+ [ $(echo "$path" | grep -c "^$BMX6_DIR") -ne 1 ] && exit 1
+}
+
+print_query() {
+ # If the query is a directory
+ [ -d "$BMX6_DIR/$1" ] &&
+ {
+ # If /all has not been specified
+ [ -z "$QALL" ] &&
+ {
+ total=$(ls $BMX6_DIR/$1 | wc -w)
+ i=1
+ echo -n "{ \"$1\": [ "
+ for f in $(ls $BMX6_DIR/$1); do
+ echo -n "{ \"name\": \"$f\" }"
+ [ $i -lt $total ] && echo -n ','
+ i=$(( $i + 1 ))
+ done
+ echo -n " ] }"
+
+ # If /all has been specified, printing all the files together
+ } || {
+ comma=""
+ echo -n "[ "
+ for entry in "$BMX6_DIR/$1/"*; do
+ [ -f "$entry" ] &&
+ {
+ ${comma:+echo "$comma"}
+ tr -d '\n' < "$entry"
+ comma=","
+ }
+ done
+ echo -n " ]"
+ }
+ }
+
+ # If the query is a file, just printing the file
+ [ -f "$BMX6_DIR/$QUERY" ] && cat "$BMX6_DIR/$QUERY";
+}
+
+if [ "${QUERY##*/}" == "all" ]; then
+ QUERY="${QUERY%/all}"
+ QALL=1
+fi
+
+
+if [ "$QUERY" == '$neighbours' ]; then
+ QALL=1
+ echo '{ "neighbours": [ '
+ echo '{ "originators": '
+ print_query originators
+ echo '}, '
+ echo '{ "descriptions": '
+ print_query descriptions
+ echo "} ] }"
+ exit 0
+
+else if [ "$QUERY" == '$tunnels' ]; then
+ bmx6 -c --jshow tunnels
+ exit 0
+
+else
+ check_path "$BMX6_DIR/$QUERY"
+ print_query $QUERY
+ exit 0
+fi
+fi
+
+ls -1F "$BMX6_DIR"
+exit 0
+
--- /dev/null
+/**\r
+ * Curry - Function currying\r
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com\r
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)\r
+ * Date: 10/4/2008\r
+ *\r
+ * @author Ariel Flesler\r
+ * @version 1.0.1\r
+ */\r
+\r
+function curry( fn ){\r
+ return function(){\r
+ var args = curry.args(arguments),\r
+ master = arguments.callee,\r
+ self = this;\r
+\r
+ return args.length >= fn.length ? fn.apply(self,args) : function(){\r
+ return master.apply( self, args.concat(curry.args(arguments)) );\r
+ };\r
+ };\r
+};\r
+\r
+curry.args = function( args ){\r
+ return Array.prototype.slice.call(args);\r
+};\r
+\r
+Function.prototype.curry = function(){\r
+ return curry(this);\r
+};
\ No newline at end of file
--- /dev/null
+/*
+ * Various algorithms and data structures, licensed under the MIT-license.
+ * (c) 2010 by Johann Philipp Strathausen <strathausen@gmail.com>
+ * http://strathausen.eu
+ *
+ */
+
+
+
+/*
+ Bellman-Ford
+
+ Path-finding algorithm, finds the shortest paths from one node to all nodes.
+
+
+ Complexity
+
+ O( |E| · |V| ), where E = edges and V = vertices (nodes)
+
+
+ Constraints
+
+ Can run on graphs with negative edge weights as long as they do not have
+ any negative weight cycles.
+
+ */
+function bellman_ford(g, source) {
+
+ /* STEP 1: initialisation */
+ for(var n in g.nodes)
+ g.nodes[n].distance = Infinity;
+ /* predecessors are implicitly null */
+ source.distance = 0;
+
+ step("Initially, all distances are infinite and all predecessors are null.");
+
+ /* STEP 2: relax each edge (this is at the heart of Bellman-Ford) */
+ /* repeat this for the number of nodes minus one */
+ for(var i = 1; i < g.nodes.length; i++)
+ /* for each edge */
+ for(var e in g.edges) {
+ var edge = g.edges[e];
+ if(edge.source.distance + edge.weight < edge.target.distance) {
+ step("Relax edge between " + edge.source.id + " and " + edge.target.id + ".");
+ edge.target.distance = edge.source.distance + edge.weight;
+ edge.target.predecessor = edge.source;
+ }
+ //Added by Jake Stothard (Needs to be tested)
+ if(!edge.style.directed) {
+ if(edge.target.distance + edge.weight < edge.source.distance) {
+ g.snapShot("Relax edge between "+edge.target.id+" and "+edge.source.id+".");
+ edge.source.distance = edge.target.distance + edge.weight;
+ edge.source.predecessor = edge.target;
+ }
+ }
+ }
+ step("Ready.");
+
+ /* STEP 3: TODO Check for negative cycles */
+ /* For now we assume here that the graph does not contain any negative
+ weights cycles. (this is left as an excercise to the reader[tm]) */
+}
+
+
+
+/*
+ Path-finding algorithm Dijkstra
+
+ - worst-case running time is O((|E| + |V|) · log |V| ) thus better than
+ Bellman-Ford for sparse graphs (with less edges), but cannot handle
+ negative edge weights
+ */
+function dijkstra(g, source) {
+
+ /* initially, all distances are infinite and all predecessors are null */
+ for(var n in g.nodes)
+ g.nodes[n].distance = Infinity;
+ /* predecessors are implicitly null */
+
+ g.snapShot("Initially, all distances are infinite and all predecessors are null.");
+
+ source.distance = 0;
+ /* set of unoptimized nodes, sorted by their distance (but a Fibonacci heap
+ would be better) */
+ var q = new BinaryMinHeap(g.nodes, "distance");
+
+ /* pointer to the node in focus */
+ var node;
+
+ /* get the node with the smallest distance
+ as long as we have unoptimized nodes. q.min() can have O(log n). */
+ while(q.min() != undefined) {
+ /* remove the latest */
+ node = q.extractMin();
+ node.optimized = true;
+
+ /* no nodes accessible from this one, should not happen */
+ if(node.distance == Infinity)
+ throw "Orphaned node!";
+
+ /* for each neighbour of node */
+ for(e in node.edges) {
+ var other = (node == node.edges[e].target) ? node.edges[e].source : node.edges[e].target;
+
+ if(other.optimized)
+ continue;
+
+ /* look for an alternative route */
+ var alt = node.distance + node.edges[e].weight;
+
+ /* update distance and route if a better one has been found */
+ if (alt < other.distance) {
+
+ /* update distance of neighbour */
+ other.distance = alt;
+
+ /* update priority queue */
+ q.heapify();
+
+ /* update path */
+ other.predecessor = node;
+ g.snapShot("Enhancing node.")
+ }
+ }
+ }
+}
+
+
+/* All-Pairs-Shortest-Paths */
+/* Runs at worst in O(|V|³) and at best in Omega(|V|³) :-)
+ complexity Sigma(|V|²) */
+/* This implementation is not yet ready for general use, but works with the
+ Dracula graph library. */
+function floyd_warshall(g, source) {
+
+ /* Step 1: initialising empty path matrix (second dimension is implicit) */
+ var path = [];
+ var next = [];
+ var n = g.nodes.length;
+
+ /* construct path matrix, initialize with Infinity */
+ for(j in g.nodes) {
+ path[j] = [];
+ next[j] = [];
+ for(i in g.nodes)
+ path[j][i] = j == i ? 0 : Infinity;
+ }
+
+ /* initialize path with edge weights */
+ for(e in g.edges)
+ path[g.edges[e].source.id][g.edges[e].target.id] = g.edges[e].weight;
+
+ /* Note: Usually, the initialisation is done by getting the edge weights
+ from a node matrix representation of the graph, not by iterating through
+ a list of edges as done here. */
+
+ /* Step 2: find best distances (the heart of Floyd-Warshall) */
+ for(k in g.nodes){
+ for(i in g.nodes) {
+ for(j in g.nodes)
+ if(path[i][j] > path[i][k] + path[k][j]) {
+ path[i][j] = path[i][k] + path[k][j];
+ /* Step 2.b: remember the path */
+ next[i][j] = k;
+ }
+ }
+ }
+
+ /* Step 3: Path reconstruction, get shortest path */
+ function getPath(i, j) {
+ if(path[i][j] == Infinity)
+ throw "There is no path.";
+ var intermediate = next[i][j];
+ if(intermediate == undefined)
+ return null;
+ else
+ return getPath(i, intermediate)
+ .concat([intermediate])
+ .concat(getPath(intermediate, j));
+ }
+
+ /* TODO use the knowledge, e.g. mark path in graph */
+}
+
+/*
+ Ford-Fulkerson
+
+ Max-Flow-Min-Cut Algorithm finding the maximum flow through a directed
+ graph from source to sink.
+
+
+ Complexity
+
+ O(E * max(f)), max(f) being the maximum flow
+
+
+ Description
+
+ As long as there is an open path through the residual graph, send the
+ minimum of the residual capacities on the path.
+
+
+ Constraints
+
+ The algorithm works only if all weights are integers. Otherwise it is
+ possible that the Ford–Fulkerson algorithm will not converge to the maximum
+ value.
+
+
+ Input
+
+ g - Graph object
+ s - Source ID
+ t - Target (sink) ID
+
+
+ Output
+
+ Maximum flow from Source s to Target t
+
+ */
+/*
+ Edmonds-Karp
+
+ Max-Flow-Min-Cut Algorithm finding the maximum flow through a directed
+ graph from source to sink. An implementation of the Ford-Fulkerson
+ algorithm.
+
+
+ Complexity
+
+ O(|V|*|E|²)
+
+
+ Input
+
+ g - Graph object (with node and edge lists, capacity is a property of edge)
+ s - source ID
+ t - sink ID
+
+ */
+function edmonds_karp(g, s, t) {
+
+}
+
+/*
+ A simple binary min-heap serving as a priority queue
+ - takes an array as the input, with elements having a key property
+ - elements will look like this:
+ {
+ key: "... key property ...",
+ value: "... element content ..."
+ }
+ - provides insert(), min(), extractMin() and heapify()
+ - example usage (e.g. via the Firebug or Chromium console):
+ var x = {foo: 20, hui: "bla"};
+ var a = new BinaryMinHeap([x,{foo:3},{foo:10},{foo:20},{foo:30},{foo:6},{foo:1},{foo:3}],"foo");
+ console.log(a.extractMin());
+ console.log(a.extractMin());
+ x.foo = 0; // update key
+ a.heapify(); // call this always after having a key updated
+ console.log(a.extractMin());
+ console.log(a.extractMin());
+ - can also be used on a simple array, like [9,7,8,5]
+ */
+function BinaryMinHeap(array, key) {
+
+ /* Binary tree stored in an array, no need for a complicated data structure */
+ var tree = [];
+
+ var key = key || 'key';
+
+ /* Calculate the index of the parent or a child */
+ var parent = function(index) { return Math.floor((index - 1)/2); };
+ var right = function(index) { return 2 * index + 2; };
+ var left = function(index) { return 2 * index + 1; };
+
+ /* Helper function to swap elements with their parent
+ as long as the parent is bigger */
+ function bubble_up(i) {
+ var p = parent(i);
+ while((p >= 0) && (tree[i][key] < tree[p][key])) {
+ /* swap with parent */
+ tree[i] = tree.splice(p, 1, tree[i])[0];
+ /* go up one level */
+ i = p;
+ p = parent(i);
+ }
+ }
+
+ /* Helper function to swap elements with the smaller of their children
+ as long as there is one */
+ function bubble_down(i) {
+ var l = left(i);
+ var r = right(i);
+
+ /* as long as there are smaller children */
+ while(tree[l] && (tree[i][key] > tree[l][key]) || tree[r] && (tree[i][key] > tree[r][key])) {
+
+ /* find smaller child */
+ var child = tree[l] ? tree[r] ? tree[l][key] > tree[r][key] ? r : l : l : l;
+
+ /* swap with smaller child with current element */
+ tree[i] = tree.splice(child, 1, tree[i])[0];
+
+ /* go up one level */
+ i = child;
+ l = left(i);
+ r = right(i);
+ }
+ }
+
+ /* Insert a new element with respect to the heap property
+ 1. Insert the element at the end
+ 2. Bubble it up until it is smaller than its parent */
+ this.insert = function(element) {
+
+ /* make sure there's a key property */
+ (element[key] == undefined) && (element = {key:element});
+
+ /* insert element at the end */
+ tree.push(element);
+
+ /* bubble up the element */
+ bubble_up(tree.length - 1);
+ }
+
+ /* Only show us the minimum */
+ this.min = function() {
+ return tree.length == 1 ? undefined : tree[0];
+ }
+
+ /* Return and remove the minimum
+ 1. Take the root as the minimum that we are looking for
+ 2. Move the last element to the root (thereby deleting the root)
+ 3. Compare the new root with both of its children, swap it with the
+ smaller child and then check again from there (bubble down)
+ */
+ this.extractMin = function() {
+ var result = this.min();
+
+ /* move the last element to the root or empty the tree completely */
+ /* bubble down the new root if necessary */
+ (tree.length == 1) && (tree = []) || (tree[0] = tree.pop()) && bubble_down(0);
+
+ return result;
+ }
+
+ /* currently unused, TODO implement */
+ this.changeKey = function(index, key) {
+ throw "function not implemented";
+ }
+
+ this.heapify = function() {
+ for(var start = Math.floor((tree.length - 2) / 2); start >= 0; start--) {
+ bubble_down(start);
+ }
+ }
+
+ /* insert the input elements one by one only when we don't have a key property (TODO can be done more elegant) */
+ for(i in (array || []))
+ this.insert(array[i]);
+}
+
+
+
+/*
+ Quick Sort:
+ 1. Select some random value from the array, the median.
+ 2. Divide the array in three smaller arrays according to the elements
+ being less, equal or greater than the median.
+ 3. Recursively sort the array containg the elements less than the
+ median and the one containing elements greater than the median.
+ 4. Concatenate the three arrays (less, equal and greater).
+ 5. One or no element is always sorted.
+ TODO: This could be implemented more efficiently by using only one array object and several pointers.
+*/
+function quickSort(arr) {
+ /* recursion anchor: one element is always sorted */
+ if(arr.length <= 1) return arr;
+ /* randomly selecting some value */
+ var median = arr[Math.floor(Math.random() * arr.length)];
+ var arr1 = [], arr2 = [], arr3 = [];
+ for(var i in arr) {
+ arr[i] < median && arr1.push(arr[i]);
+ arr[i] == median && arr2.push(arr[i]);
+ arr[i] > median && arr3.push(arr[i]);
+ }
+ /* recursive sorting and assembling final result */
+ return quickSort(arr1).concat(arr2).concat(quickSort(arr3));
+}
+
+/*
+ Selection Sort:
+ 1. Select the minimum and remove it from the array
+ 2. Sort the rest recursively
+ 3. Return the minimum plus the sorted rest
+ 4. An array with only one element is already sorted
+*/
+function selectionSort(arr) {
+ /* recursion anchor: one element is always sorted */
+ if(arr.length == 1) return arr;
+ var minimum = Infinity;
+ var index;
+ for(var i in arr) {
+ if(arr[i] < minimum) {
+ minimum = arr[i];
+ index = i; /* remember the minimum index for later removal */
+ }
+ }
+ /* remove the minimum */
+ arr.splice(index, 1);
+ /* assemble result and sort recursively (could be easily done iteratively as well)*/
+ return [minimum].concat(selectionSort(arr));
+}
+
+/*
+ Merge Sort:
+ 1. Cut the array in half
+ 2. Sort each of them recursively
+ 3. Merge the two sorted arrays
+ 4. An array with only one element is considered sorted
+
+*/
+function mergeSort(arr) {
+ /* merges two sorted arrays into one sorted array */
+ function merge(a, b) {
+ /* result set */
+ var c = [];
+ /* as long as there are elements in the arrays to be merged */
+ while(a.length > 0 || b.length > 0){
+ /* are there elements to be merged, if yes, compare them and merge */
+ var n = a.length > 0 && b.length > 0 ? a[0] < b[0] ? a.shift() : b.shift() : b.length > 0 ? b.shift() : a.length > 0 ? a.shift() : null;
+ /* always push the smaller one onto the result set */
+ n != null && c.push(n);
+ }
+ return c;
+ }
+ /* this mergeSort implementation cuts the array in half, wich should be fine with randomized arrays, but introduces the risk of a worst-case scenario */
+ median = Math.floor(arr.length / 2);
+ var part1 = arr.slice(0, median); /* for some reason it doesn't work if inserted directly in the return statement (tried so with firefox) */
+ var part2 = arr.slice(median - arr.length);
+ return arr.length <= 1 ? arr : merge(
+ mergeSort(part1), /* first half */
+ mergeSort(part2) /* second half */
+ );
+}
+
+/* Balanced Red-Black-Tree */
+function RedBlackTree(arr) {
+
+}
+
+function BTree(arr) {
+
+}
+
+function NaryTree(n, arr) {
+
+}
+
+/**
+ * Knuth-Morris-Pratt string matching algorithm - finds a pattern in a text.
+ * FIXME: Doesn't work correctly yet.
+ */
+function kmp(p, t) {
+
+ /**
+ * PREFIX, OVERLAP or FALIURE function for KMP. Computes how many iterations
+ * the algorithm can skip after a mismatch.
+ *
+ * @input p - pattern (string)
+ * @result array of skippable iterations
+ */
+ function prefix(p) {
+ /* pi contains the computed skip marks */
+ var pi = [0], k = 0;
+ for(q = 1; q < p.length; q++) {
+ while(k > 0 && (p.charAt(k) != p.charAt(q)))
+ k = pi[k-1];
+
+ (p.charAt(k) == p.charAt(q)) && k++;
+
+ pi[q] = k;
+ }
+ return pi;
+ }
+
+ /* The actual KMP algorithm starts here. */
+
+ var pi = prefix(p), q = 0, result = [];
+
+ for(var i = 0; i < t.length; i++) {
+ /* jump forward as long as the character doesn't match */
+ while((q > 0) && (p.charAt(q) != t.charAt(i)))
+ q = pi[q];
+
+ (p.charAt(q) == t.charAt(i)) && q++;
+
+ (q == p.length) && result.push(i - p.length) && (q = pi[q]);
+ }
+
+ return result;
+}
+
+/* step for algorithm visualisation */
+function step(comment, funct) {
+ //wait for input
+ //display comment (before or after waiting)
+// next.wait();
+ /* execute callback function */
+ funct();
+}
+
+/**
+ * Curry - Function currying
+ * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
+ * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
+ * Date: 10/4/2008
+ *
+ * @author Ariel Flesler
+ * @version 1.0.1
+ */
+function curry( fn ){
+ return function(){
+ var args = curry.args(arguments),
+ master = arguments.callee,
+ self = this;
+
+ return args.length >= fn.length ? fn.apply(self,args) : function(){
+ return master.apply( self, args.concat(curry.args(arguments)) );
+ };
+ };
+};
+
+curry.args = function( args ){
+ return Array.prototype.slice.call(args);
+};
+
+Function.prototype.curry = function(){
+ return curry(this);
+};
+
+/**
+ * Topological Sort
+ *
+ * Sort a directed graph based on incoming edges
+ *
+ * Coded by Jake Stothard
+ */
+function topological_sort(g) {
+ //Mark nodes as "deleted" instead of actually deleting them
+ //That way we don't have to copy g
+
+ for(i in g.nodes)
+ g.nodes[i].deleted = false;
+
+ var ret = topological_sort_helper(g);
+
+ //Cleanup: Remove the deleted property
+ for(i in g.nodes)
+ delete g.nodes[i].deleted
+
+ return ret;
+}
+function topological_sort_helper(g) {
+ //Find node with no incoming edges
+ var node;
+ for(i in g.nodes) {
+ if(g.nodes[i].deleted)
+ continue; //Bad style, meh
+
+ var incoming = false;
+ for(j in g.nodes[i].edges) {
+ if(g.nodes[i].edges[j].target == g.nodes[i]
+ && g.nodes[i].edges[j].source.deleted == false) {
+ incoming = true;
+ break;
+ }
+ }
+ if(!incoming) {
+ node = g.nodes[i];
+ break;
+ }
+ }
+
+ // Either unsortable or done. Either way, GTFO
+ if(node == undefined)
+ return [];
+
+ //"Delete" node from g
+ node.deleted = true;
+
+ var tail = topological_sort_helper(g);
+
+ tail.unshift(node);
+
+ return tail;
+}
--- /dev/null
+/**
+ * Originally grabbed from the official RaphaelJS Documentation
+ * http://raphaeljs.com/graffle.html
+ * Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de
+ * Licenced under the MIT licence.
+ */
+
+/**
+ * Usage:
+ * connect two shapes
+ * parameters:
+ * source shape [or connection for redrawing],
+ * target shape,
+ * style with { fg : linecolor, bg : background color, directed: boolean }
+ * returns:
+ * connection { draw = function() }
+ */
+Raphael.fn.connection = function (obj1, obj2, style) {
+ var selfRef = this;
+ /* create and return new connection */
+ var edge = {/*
+ from : obj1,
+ to : obj2,
+ style : style,*/
+ draw : function() {
+ /* get bounding boxes of target and source */
+ var bb1 = obj1.getBBox();
+ var bb2 = obj2.getBBox();
+ var off1 = 0;
+ var off2 = 0;
+ /* coordinates for potential connection coordinates from/to the objects */
+ var p = [
+ {x: bb1.x + bb1.width / 2, y: bb1.y - off1}, /* NORTH 1 */
+ {x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1}, /* SOUTH 1 */
+ {x: bb1.x - off1, y: bb1.y + bb1.height / 2}, /* WEST 1 */
+ {x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2}, /* EAST 1 */
+ {x: bb2.x + bb2.width / 2, y: bb2.y - off2}, /* NORTH 2 */
+ {x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2}, /* SOUTH 2 */
+ {x: bb2.x - off2, y: bb2.y + bb2.height / 2}, /* WEST 2 */
+ {x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2} /* EAST 2 */
+ ];
+
+ /* distances between objects and according coordinates connection */
+ var d = {}, dis = [];
+
+ /*
+ * find out the best connection coordinates by trying all possible ways
+ */
+ /* loop the first object's connection coordinates */
+ for (var i = 0; i < 4; i++) {
+ /* loop the seond object's connection coordinates */
+ for (var j = 4; j < 8; j++) {
+ var dx = Math.abs(p[i].x - p[j].x),
+ dy = Math.abs(p[i].y - p[j].y);
+ if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
+ dis.push(dx + dy);
+ d[dis[dis.length - 1].toFixed(3)] = [i, j];
+ }
+ }
+ }
+ var res = dis.length == 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)];
+ /* bezier path */
+ var x1 = p[res[0]].x,
+ y1 = p[res[0]].y,
+ x4 = p[res[1]].x,
+ y4 = p[res[1]].y,
+ dx = Math.max(Math.abs(x1 - x4) / 2, 10),
+ dy = Math.max(Math.abs(y1 - y4) / 2, 10),
+ x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
+ y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
+ x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
+ y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
+ /* assemble path and arrow */
+ var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
+ /* arrow */
+ if(style && style.directed) {
+ /* magnitude, length of the last path vector */
+ var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3));
+ /* vector normalisation to specified length */
+ var norm = function(x,l){return (-x*(l||5)/mag);};
+ /* calculate array coordinates (two lines orthogonal to the path vector) */
+ var arr = [
+ {x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3)},
+ {x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3)}
+ ];
+ path = path + ",M"+arr[0].x+","+arr[0].y+",L"+x4+","+y4+",L"+arr[1].x+","+arr[1].y;
+ }
+ /* function to be used for moving existent path(s), e.g. animate() or attr() */
+ var move = "attr";
+ /* applying path(s) */
+ edge.fg && edge.fg[move]({path:path})
+ || (edge.fg = selfRef.path(path).attr({stroke: style && style.stroke || "#000", fill: "none"}).toBack());
+ edge.bg && edge.bg[move]({path:path})
+ || style && style.fill && (edge.bg = style.fill.split && selfRef.path(path).attr({stroke: style.fill.split("|")[0], fill: "none", "stroke-width": style.fill.split("|")[1] || 3}).toBack());
+ /* setting label */
+ style && style.label
+ && (edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2})
+ || (edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label).attr({fill: "#000", "font-size": style["font-size"] || "12px"})));
+ style && style.label && style["label-style"] && edge.label && edge.label.attr(style["label-style"]);
+ style && style.callback && style.callback(edge);
+ }
+ }
+ edge.draw();
+ return edge;
+};
+//Raphael.prototype.set.prototype.dodo=function(){console.log("works");};
--- /dev/null
+/*
+ * Dracula Graph Layout and Drawing Framework 0.0.3alpha
+ * (c) 2010 Philipp Strathausen <strathausen@gmail.com>, http://strathausen.eu
+ * Contributions by Jake Stothard <stothardj@gmail.com>.
+ *
+ * based on the Graph JavaScript framework, version 0.0.1
+ * (c) 2006 Aslak Hellesoy <aslak.hellesoy@gmail.com>
+ * (c) 2006 Dave Hoover <dave.hoover@gmail.com>
+ *
+ * Ported from Graph::Layouter::Spring in
+ * http://search.cpan.org/~pasky/Graph-Layderer-0.02/
+ * The algorithm is based on a spring-style layouter of a Java-based social
+ * network tracker PieSpy written by Paul Mutton <paul@jibble.org>.
+ *
+ * This code is freely distributable under the MIT license. Commercial use is
+ * hereby granted without any cost or restriction.
+ *
+ * Links:
+ *
+ * Graph Dracula JavaScript Framework:
+ * http://graphdracula.net
+ *
+ /*--------------------------------------------------------------------------*/
+
+/*
+ * Edge Factory
+ */
+var AbstractEdge = function() {
+}
+AbstractEdge.prototype = {
+ hide: function() {
+ this.connection.fg.hide();
+ this.connection.bg && this.bg.connection.hide();
+ }
+};
+var EdgeFactory = function() {
+ this.template = new AbstractEdge();
+ this.template.style = new Object();
+ this.template.style.directed = false;
+ this.template.weight = 1;
+};
+EdgeFactory.prototype = {
+ build: function(source, target) {
+ var e = jQuery.extend(true, {}, this.template);
+ e.source = source;
+ e.target = target;
+ return e;
+ }
+};
+
+/*
+ * Graph
+ */
+var Graph = function() {
+ this.nodes = {};
+ this.edges = [];
+ this.snapshots = []; // previous graph states TODO to be implemented
+ this.edgeFactory = new EdgeFactory();
+};
+Graph.prototype = {
+ /*
+ * add a node
+ * @id the node's ID (string or number)
+ * @content (optional, dictionary) can contain any information that is
+ * being interpreted by the layout algorithm or the graph
+ * representation
+ */
+ addNode: function(id, content) {
+ /* testing if node is already existing in the graph */
+ if(this.nodes[id] == undefined) {
+ this.nodes[id] = new Graph.Node(id, content);
+ }
+ return this.nodes[id];
+ },
+
+ addEdge: function(source, target, style) {
+ var s = this.addNode(source);
+ var t = this.addNode(target);
+ var edge = this.edgeFactory.build(s, t);
+ jQuery.extend(edge.style,style);
+ s.edges.push(edge);
+ this.edges.push(edge);
+ // NOTE: Even directed edges are added to both nodes.
+ t.edges.push(edge);
+ },
+
+ /* TODO to be implemented
+ * Preserve a copy of the graph state (nodes, positions, ...)
+ * @comment a comment describing the state
+ */
+ snapShot: function(comment) {
+ /* FIXME
+ var graph = new Graph();
+ graph.nodes = jQuery.extend(true, {}, this.nodes);
+ graph.edges = jQuery.extend(true, {}, this.edges);
+ this.snapshots.push({comment: comment, graph: graph});
+ */
+ },
+ removeNode: function(id) {
+ delete this.nodes[id];
+ for(var i = 0; i < this.edges.length; i++) {
+ if (this.edges[i].source.id == id || this.edges[i].target.id == id) {
+ this.edges.splice(i, 1);
+ i--;
+ }
+ }
+ }
+};
+
+/*
+ * Node
+ */
+Graph.Node = function(id, node){
+ node = node || {};
+ node.id = id;
+ node.edges = [];
+ node.hide = function() {
+ this.hidden = true;
+ this.shape && this.shape.hide(); /* FIXME this is representation specific code and should be elsewhere */
+ for(i in this.edges)
+ (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].hide && this.edges[i].hide();
+ };
+ node.show = function() {
+ this.hidden = false;
+ this.shape && this.shape.show();
+ for(i in this.edges)
+ (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].show && this.edges[i].show();
+ };
+ return node;
+};
+Graph.Node.prototype = {
+};
+
+/*
+ * Renderer base class
+ */
+Graph.Renderer = {};
+
+/*
+ * Renderer implementation using RaphaelJS
+ */
+Graph.Renderer.Raphael = function(element, graph, width, height) {
+ this.width = width || 400;
+ this.height = height || 400;
+ var selfRef = this;
+ this.r = Raphael(element, this.width, this.height);
+ this.radius = 40; /* max dimension of a node */
+ this.graph = graph;
+ this.mouse_in = false;
+
+ /* TODO default node rendering function */
+ if(!this.graph.render) {
+ this.graph.render = function() {
+ return;
+ }
+ }
+
+ /*
+ * Dragging
+ */
+ this.isDrag = false;
+ this.dragger = function (e) {
+ this.dx = e.clientX;
+ this.dy = e.clientY;
+ selfRef.isDrag = this;
+ this.set && this.set.animate({"fill-opacity": .1}, 200) && this.set.toFront();
+ e.preventDefault && e.preventDefault();
+ };
+
+ var d = document.getElementById(element);
+ d.onmousemove = function (e) {
+ e = e || window.event;
+ if (selfRef.isDrag) {
+ var bBox = selfRef.isDrag.set.getBBox();
+ // TODO round the coordinates here (eg. for proper image representation)
+ var newX = e.clientX - selfRef.isDrag.dx + (bBox.x + bBox.width / 2);
+ var newY = e.clientY - selfRef.isDrag.dy + (bBox.y + bBox.height / 2);
+ /* prevent shapes from being dragged out of the canvas */
+ var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0);
+ var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0);
+ selfRef.isDrag.set.translate(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));
+ // console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));
+ for (var i in selfRef.graph.edges) {
+ selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw();
+ }
+ //selfRef.r.safari();
+ selfRef.isDrag.dx = clientX;
+ selfRef.isDrag.dy = clientY;
+ }
+ };
+ d.onmouseup = function () {
+ selfRef.isDrag && selfRef.isDrag.set.animate({"fill-opacity": .6}, 500);
+ selfRef.isDrag = false;
+ };
+ this.draw();
+};
+Graph.Renderer.Raphael.prototype = {
+ translate: function(point) {
+ return [
+ (point[0] - this.graph.layoutMinX) * this.factorX + this.radius,
+ (point[1] - this.graph.layoutMinY) * this.factorY + this.radius
+ ];
+ },
+
+ rotate: function(point, length, angle) {
+ var dx = length * Math.cos(angle);
+ var dy = length * Math.sin(angle);
+ return [point[0]+dx, point[1]+dy];
+ },
+
+ draw: function() {
+ this.factorX = (this.width - 2 * this.radius) / (this.graph.layoutMaxX - this.graph.layoutMinX);
+ this.factorY = (this.height - 2 * this.radius) / (this.graph.layoutMaxY - this.graph.layoutMinY);
+ for (i in this.graph.nodes) {
+ this.drawNode(this.graph.nodes[i]);
+ }
+ for (var i = 0; i < this.graph.edges.length; i++) {
+ this.drawEdge(this.graph.edges[i]);
+ }
+ },
+
+ drawNode: function(node) {
+ var point = this.translate([node.layoutPosX, node.layoutPosY]);
+ node.point = point;
+
+ /* if node has already been drawn, move the nodes */
+ if(node.shape) {
+ var oBBox = node.shape.getBBox();
+ var opoint = { x: oBBox.x + oBBox.width / 2, y: oBBox.y + oBBox.height / 2};
+ node.shape.translate(Math.round(point[0] - opoint.x), Math.round(point[1] - opoint.y));
+ this.r.safari();
+ return node;
+ }/* else, draw new nodes */
+
+ var shape;
+
+ /* if a node renderer function is provided by the user, then use it
+ or the default render function instead */
+ if(!node.render) {
+ node.render = function(r, node) {
+ /* the default node drawing */
+ var color = Raphael.getColor();
+ var ellipse = r.ellipse(0, 0, 30, 20).attr({fill: color, stroke: color, "stroke-width": 2});
+ /* set DOM node ID */
+ ellipse.node.id = node.label || node.id;
+ shape = r.set().
+ push(ellipse).
+ push(r.text(0, 30, node.label || node.id));
+ return shape;
+ }
+ }
+ /* or check for an ajax representation of the nodes */
+ if(node.shapes) {
+ // TODO ajax representation evaluation
+ }
+
+ shape = node.render(this.r, node).hide();
+
+ shape.attr({"fill-opacity": .6});
+ /* re-reference to the node an element belongs to, needed for dragging all elements of a node */
+ shape.items.forEach(function(item){ item.set = shape; item.node.style.cursor = "move"; });
+ shape.mousedown(this.dragger);
+
+ var box = shape.getBBox();
+ shape.translate(Math.round(point[0]-(box.x+box.width/2)),Math.round(point[1]-(box.y+box.height/2)))
+ //console.log(box,point);
+ node.hidden || shape.show();
+ node.shape = shape;
+ },
+ drawEdge: function(edge) {
+ /* if this edge already exists the other way around and is undirected */
+ if(edge.backedge)
+ return;
+ if(edge.source.hidden || edge.target.hidden) {
+ edge.connection && edge.connection.fg.hide() | edge.connection.bg && edge.connection.bg.hide();
+ return;
+ }
+ /* if edge already has been drawn, only refresh the edge */
+ if(!edge.connection) {
+ edge.style && edge.style.callback && edge.style.callback(edge); // TODO move this somewhere else
+ edge.connection = this.r.connection(edge.source.shape, edge.target.shape, edge.style);
+ return;
+ }
+ //FIXME showing doesn't work well
+ edge.connection.fg.show();
+ edge.connection.bg && edge.connection.bg.show();
+ edge.connection.draw();
+ }
+};
+Graph.Layout = {};
+Graph.Layout.Spring = function(graph) {
+ this.graph = graph;
+ this.iterations = 500;
+ this.maxRepulsiveForceDistance = 6;
+ this.k = 2;
+ this.c = 0.01;
+ this.maxVertexMovement = 0.5;
+ this.layout();
+};
+Graph.Layout.Spring.prototype = {
+ layout: function() {
+ this.layoutPrepare();
+ for (var i = 0; i < this.iterations; i++) {
+ this.layoutIteration();
+ }
+ this.layoutCalcBounds();
+ },
+
+ layoutPrepare: function() {
+ for (i in this.graph.nodes) {
+ var node = this.graph.nodes[i];
+ node.layoutPosX = 0;
+ node.layoutPosY = 0;
+ node.layoutForceX = 0;
+ node.layoutForceY = 0;
+ }
+
+ },
+
+ layoutCalcBounds: function() {
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
+
+ for (i in this.graph.nodes) {
+ var x = this.graph.nodes[i].layoutPosX;
+ var y = this.graph.nodes[i].layoutPosY;
+
+ if(x > maxx) maxx = x;
+ if(x < minx) minx = x;
+ if(y > maxy) maxy = y;
+ if(y < miny) miny = y;
+ }
+
+ this.graph.layoutMinX = minx;
+ this.graph.layoutMaxX = maxx;
+ this.graph.layoutMinY = miny;
+ this.graph.layoutMaxY = maxy;
+ },
+
+ layoutIteration: function() {
+ // Forces on nodes due to node-node repulsions
+
+ var prev = new Array();
+ for(var c in this.graph.nodes) {
+ var node1 = this.graph.nodes[c];
+ for (var d in prev) {
+ var node2 = this.graph.nodes[prev[d]];
+ this.layoutRepulsive(node1, node2);
+
+ }
+ prev.push(c);
+ }
+
+ // Forces on nodes due to edge attractions
+ for (var i = 0; i < this.graph.edges.length; i++) {
+ var edge = this.graph.edges[i];
+ this.layoutAttractive(edge);
+ }
+
+ // Move by the given force
+ for (i in this.graph.nodes) {
+ var node = this.graph.nodes[i];
+ var xmove = this.c * node.layoutForceX;
+ var ymove = this.c * node.layoutForceY;
+
+ var max = this.maxVertexMovement;
+ if(xmove > max) xmove = max;
+ if(xmove < -max) xmove = -max;
+ if(ymove > max) ymove = max;
+ if(ymove < -max) ymove = -max;
+
+ node.layoutPosX += xmove;
+ node.layoutPosY += ymove;
+ node.layoutForceX = 0;
+ node.layoutForceY = 0;
+ }
+ },
+
+ layoutRepulsive: function(node1, node2) {
+ if (typeof node1 == 'undefined' || typeof node2 == 'undefined')
+ return;
+ var dx = node2.layoutPosX - node1.layoutPosX;
+ var dy = node2.layoutPosY - node1.layoutPosY;
+ var d2 = dx * dx + dy * dy;
+ if(d2 < 0.01) {
+ dx = 0.1 * Math.random() + 0.1;
+ dy = 0.1 * Math.random() + 0.1;
+ var d2 = dx * dx + dy * dy;
+ }
+ var d = Math.sqrt(d2);
+ if(d < this.maxRepulsiveForceDistance) {
+ var repulsiveForce = this.k * this.k / d;
+ node2.layoutForceX += repulsiveForce * dx / d;
+ node2.layoutForceY += repulsiveForce * dy / d;
+ node1.layoutForceX -= repulsiveForce * dx / d;
+ node1.layoutForceY -= repulsiveForce * dy / d;
+ }
+ },
+
+ layoutAttractive: function(edge) {
+ var node1 = edge.source;
+ var node2 = edge.target;
+
+ var dx = node2.layoutPosX - node1.layoutPosX;
+ var dy = node2.layoutPosY - node1.layoutPosY;
+ var d2 = dx * dx + dy * dy;
+ if(d2 < 0.01) {
+ dx = 0.1 * Math.random() + 0.1;
+ dy = 0.1 * Math.random() + 0.1;
+ var d2 = dx * dx + dy * dy;
+ }
+ var d = Math.sqrt(d2);
+ if(d > this.maxRepulsiveForceDistance) {
+ d = this.maxRepulsiveForceDistance;
+ d2 = d * d;
+ }
+ var attractiveForce = (d2 - this.k * this.k) / this.k;
+ if(edge.attraction == undefined) edge.attraction = 1;
+ attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
+
+ node2.layoutForceX -= attractiveForce * dx / d;
+ node2.layoutForceY -= attractiveForce * dy / d;
+ node1.layoutForceX += attractiveForce * dx / d;
+ node1.layoutForceY += attractiveForce * dy / d;
+ }
+};
+
+Graph.Layout.Ordered = function(graph, order) {
+ this.graph = graph;
+ this.order = order;
+ this.layout();
+};
+Graph.Layout.Ordered.prototype = {
+ layout: function() {
+ this.layoutPrepare();
+ this.layoutCalcBounds();
+ },
+
+ layoutPrepare: function(order) {
+ for (i in this.graph.nodes) {
+ var node = this.graph.nodes[i];
+ node.layoutPosX = 0;
+ node.layoutPosY = 0;
+ }
+ var counter = 0;
+ for (i in this.order) {
+ var node = this.order[i];
+ node.layoutPosX = counter;
+ node.layoutPosY = Math.random();
+ counter++;
+ }
+ },
+
+ layoutCalcBounds: function() {
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
+
+ for (i in this.graph.nodes) {
+ var x = this.graph.nodes[i].layoutPosX;
+ var y = this.graph.nodes[i].layoutPosY;
+
+ if(x > maxx) maxx = x;
+ if(x < minx) minx = x;
+ if(y > maxy) maxy = y;
+ if(y < miny) miny = y;
+ }
+
+ this.graph.layoutMinX = minx;
+ this.graph.layoutMaxX = maxx;
+
+ this.graph.layoutMinY = miny;
+ this.graph.layoutMaxY = maxy;
+ }
+};
+
+/*
+ * usefull JavaScript extensions,
+ */
+
+function log(a) {console.log&&console.log(a);}
+
+/*
+ * Raphael Tooltip Plugin
+ * - attaches an element as a tooltip to another element
+ *
+ * Usage example, adding a rectangle as a tooltip to a circle:
+ *
+ * paper.circle(100,100,10).tooltip(paper.rect(0,0,20,30));
+ *
+ * If you want to use more shapes, you'll have to put them into a set.
+ *
+ */
+Raphael.el.tooltip = function (tp) {
+ this.tp = tp;
+ this.tp.o = {x: 0, y: 0};
+ this.tp.hide();
+ this.hover(
+ function(event){
+ this.mousemove(function(event){
+ this.tp.translate(event.clientX -
+ this.tp.o.x,event.clientY - this.tp.o.y);
+ this.tp.o = {x: event.clientX, y: event.clientY};
+ });
+ this.tp.show().toFront();
+ },
+ function(event){
+ this.tp.hide();
+ this.unmousemove();
+ });
+ return this;
+};
+
+/* For IE */
+if (!Array.prototype.forEach)
+{
+ Array.prototype.forEach = function(fun /*, thisp*/)
+ {
+ var len = this.length;
+ if (typeof fun != "function")
+ throw new TypeError();
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++)
+ {
+ if (i in this)
+ fun.call(thisp, this[i], i, this);
+ }
+ };
+}
--- /dev/null
+/*!
+ * jQuery JavaScript Library v1.4.2
+ * http://jquery.com/
+ *
+ * Copyright 2010, John Resig
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ * Copyright 2010, The Dojo Foundation
+ * Released under the MIT, BSD, and GPL Licenses.
+ *
+ * Date: Sat Feb 13 22:33:48 2010 -0500
+ */
+(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o<i;o++)e(a[o],b,f?d.call(a[o],o,e(a[o],b)):d,j);return a}return i?
+e(a[0],b):w}function J(){return(new Date).getTime()}function Y(){return false}function Z(){return true}function na(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function oa(a){var b,d=[],f=[],e=arguments,j,i,o,k,n,r;i=c.data(this,"events");if(!(a.liveFired===this||!i||!i.live||a.button&&a.type==="click")){a.liveFired=this;var u=i.live.slice(0);for(k=0;k<u.length;k++){i=u[k];i.origType.replace(O,"")===a.type?f.push(i.selector):u.splice(k--,1)}j=c(a.target).closest(f,a.currentTarget);n=0;for(r=
+j.length;n<r;n++)for(k=0;k<u.length;k++){i=u[k];if(j[n].selector===i.selector){o=j[n].elem;f=null;if(i.preType==="mouseenter"||i.preType==="mouseleave")f=c(a.relatedTarget).closest(i.selector)[0];if(!f||f!==o)d.push({elem:o,handleObj:i})}}n=0;for(r=d.length;n<r;n++){j=d[n];a.currentTarget=j.elem;a.data=j.handleObj.data;a.handleObj=j.handleObj;if(j.handleObj.origHandler.apply(j.elem,e)===false){b=false;break}}return b}}function pa(a,b){return"live."+(a&&a!=="*"?a+".":"")+b.replace(/\./g,"`").replace(/ /g,
+"&")}function qa(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function ra(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var f=c.data(a[d++]),e=c.data(this,f);if(f=f&&f.events){delete e.handle;e.events={};for(var j in f)for(var i in f[j])c.event.add(this,j,f[j][i],f[j][i].data)}}})}function sa(a,b,d){var f,e,j;b=b&&b[0]?b[0].ownerDocument||b[0]:s;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===s&&!ta.test(a[0])&&(c.support.checkClone||!ua.test(a[0]))){e=
+true;if(j=c.fragments[a[0]])if(j!==1)f=j}if(!f){f=b.createDocumentFragment();c.clean(a,b,f,d)}if(e)c.fragments[a[0]]=j?f:1;return{fragment:f,cacheable:e}}function K(a,b){var d={};c.each(va.concat.apply([],va.slice(0,b)),function(){d[this]=a});return d}function wa(a){return"scrollTo"in a&&a.document?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var c=function(a,b){return new c.fn.init(a,b)},Ra=A.jQuery,Sa=A.$,s=A.document,T,Ta=/^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/,
+Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&&
+(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this,
+a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b===
+"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,
+function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b<d;b++)if((e=arguments[b])!=null)for(j in e){i=a[j];o=e[j];if(a!==o)if(f&&o&&(c.isPlainObject(o)||c.isArray(o))){i=i&&(c.isPlainObject(i)||
+c.isArray(i))?i:c.isArray(o)?[]:{};a[j]=c.extend(f,i,o)}else if(o!==w)a[j]=o}return a};c.extend({noConflict:function(a){A.$=Sa;if(a)A.jQuery=Ra;return c},isReady:false,ready:function(){if(!c.isReady){if(!s.body)return setTimeout(c.ready,13);c.isReady=true;if(Q){for(var a,b=0;a=Q[b++];)a.call(s,c);Q=null}c.fn.triggerHandler&&c(s).triggerHandler("ready")}},bindReady:function(){if(!xa){xa=true;if(s.readyState==="complete")return c.ready();if(s.addEventListener){s.addEventListener("DOMContentLoaded",
+L,false);A.addEventListener("load",c.ready,false)}else if(s.attachEvent){s.attachEvent("onreadystatechange",L);A.attachEvent("onload",c.ready);var a=false;try{a=A.frameElement==null}catch(b){}s.documentElement.doScroll&&a&&ma()}}},isFunction:function(a){return $.call(a)==="[object Function]"},isArray:function(a){return $.call(a)==="[object Array]"},isPlainObject:function(a){if(!a||$.call(a)!=="[object Object]"||a.nodeType||a.setInterval)return false;if(a.constructor&&!aa.call(a,"constructor")&&!aa.call(a.constructor.prototype,
+"isPrototypeOf"))return false;var b;for(b in a);return b===w||aa.call(a,b)},isEmptyObject:function(a){for(var b in a)return false;return true},error:function(a){throw a;},parseJSON:function(a){if(typeof a!=="string"||!a)return null;a=c.trim(a);if(/^[\],:{}\s]*$/.test(a.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return A.JSON&&A.JSON.parse?A.JSON.parse(a):(new Function("return "+
+a))();else c.error("Invalid JSON: "+a)},noop:function(){},globalEval:function(a){if(a&&Va.test(a)){var b=s.getElementsByTagName("head")[0]||s.documentElement,d=s.createElement("script");d.type="text/javascript";if(c.support.scriptEval)d.appendChild(s.createTextNode(a));else d.text=a;b.insertBefore(d,b.firstChild);b.removeChild(d)}},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,b,d){var f,e=0,j=a.length,i=j===w||c.isFunction(a);if(d)if(i)for(f in a){if(b.apply(a[f],
+d)===false)break}else for(;e<j;){if(b.apply(a[e++],d)===false)break}else if(i)for(f in a){if(b.call(a[f],f,a[f])===false)break}else for(d=a[0];e<j&&b.call(d,e,d)!==false;d=a[++e]);return a},trim:function(a){return(a||"").replace(Wa,"")},makeArray:function(a,b){b=b||[];if(a!=null)a.length==null||typeof a==="string"||c.isFunction(a)||typeof a!=="function"&&a.setInterval?ba.call(b,a):c.merge(b,a);return b},inArray:function(a,b){if(b.indexOf)return b.indexOf(a);for(var d=0,f=b.length;d<f;d++)if(b[d]===
+a)return d;return-1},merge:function(a,b){var d=a.length,f=0;if(typeof b.length==="number")for(var e=b.length;f<e;f++)a[d++]=b[f];else for(;b[f]!==w;)a[d++]=b[f++];a.length=d;return a},grep:function(a,b,d){for(var f=[],e=0,j=a.length;e<j;e++)!d!==!b(a[e],e)&&f.push(a[e]);return f},map:function(a,b,d){for(var f=[],e,j=0,i=a.length;j<i;j++){e=b(a[j],j,d);if(e!=null)f[f.length]=e}return f.concat.apply([],f)},guid:1,proxy:function(a,b,d){if(arguments.length===2)if(typeof b==="string"){d=a;a=d[b];b=w}else if(b&&
+!c.isFunction(b)){d=b;b=w}if(!b&&a)b=function(){return a.apply(d||this,arguments)};if(a)b.guid=a.guid=a.guid||b.guid||c.guid++;return b},uaMatch:function(a){a=a.toLowerCase();a=/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version)?[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||!/compatible/.test(a)&&/(mozilla)(?:.*? rv:([\w.]+))?/.exec(a)||[];return{browser:a[1]||"",version:a[2]||"0"}},browser:{}});P=c.uaMatch(P);if(P.browser){c.browser[P.browser]=true;c.browser.version=P.version}if(c.browser.webkit)c.browser.safari=
+true;if(ya)c.inArray=function(a,b){return ya.call(b,a)};T=c(s);if(s.addEventListener)L=function(){s.removeEventListener("DOMContentLoaded",L,false);c.ready()};else if(s.attachEvent)L=function(){if(s.readyState==="complete"){s.detachEvent("onreadystatechange",L);c.ready()}};(function(){c.support={};var a=s.documentElement,b=s.createElement("script"),d=s.createElement("div"),f="script"+J();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
+var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,
+parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent=
+false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n=
+s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true,
+applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando];
+else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,
+a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===
+w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i,
+cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1)if(e.className){for(var j=" "+e.className+" ",
+i=e.className,o=0,k=b.length;o<k;o++)if(j.indexOf(" "+b[o]+" ")<0)i+=" "+b[o];e.className=c.trim(i)}else e.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(k){var n=c(this);n.removeClass(a.call(this,k,n.attr("class")))});if(a&&typeof a==="string"||a===w)for(var b=(a||"").split(ca),d=0,f=this.length;d<f;d++){var e=this[d];if(e.nodeType===1&&e.className)if(a){for(var j=(" "+e.className+" ").replace(Aa," "),i=0,o=b.length;i<o;i++)j=j.replace(" "+b[i]+" ",
+" ");e.className=c.trim(j)}else e.className=""}return this},toggleClass:function(a,b){var d=typeof a,f=typeof b==="boolean";if(c.isFunction(a))return this.each(function(e){var j=c(this);j.toggleClass(a.call(this,e,j.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var e,j=0,i=c(this),o=b,k=a.split(ca);e=k[j++];){o=f?o:!i.hasClass(e);i[o?"addClass":"removeClass"](e)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=
+this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(Aa," ").indexOf(a)>-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j<d;j++){var i=
+e[j];if(i.selected){a=c(i).val();if(b)return a;f.push(a)}}return f}if(Ba.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Za,"")}return w}var o=c.isFunction(a);return this.each(function(k){var n=c(this),r=a;if(this.nodeType===1){if(o)r=a.call(this,k,n.val());if(typeof r==="number")r+="";if(c.isArray(r)&&Ba.test(this.type))this.checked=c.inArray(n.val(),r)>=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected=
+c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
+a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g,
+function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split(".");
+k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a),
+C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B<r.length;B++){u=r[B];if(d.guid===u.guid){if(i||k.test(u.namespace)){f==null&&r.splice(B--,1);n.remove&&n.remove.call(a,u)}if(f!=
+null)break}}if(r.length===0||f!=null&&r.length===1){if(!n.teardown||n.teardown.call(a,o)===false)Ca(a,e,z.handle);delete C[e]}}else for(var B=0;B<r.length;B++){u=r[B];if(i||k.test(u.namespace)){c.event.remove(a,n,u.handler,B);r.splice(B--,1)}}}if(c.isEmptyObject(C)){if(b=z.handle)b.elem=null;delete z.events;delete z.handle;c.isEmptyObject(z)&&c.removeData(a)}}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[G]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=
+e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&&
+f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;
+if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e<j;e++){var i=d[e];if(b||f.test(i.namespace)){a.handler=i.handler;a.data=i.data;a.handleObj=i;i=i.handler.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
+fix:function(a){if(a[G])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement||s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,a.origType,c.extend({},a,{handler:oa}))},remove:function(a){var b=true,d=a.origType.replace(O,"");c.each(c.data(this,
+"events").live||[],function(){if(d===this.origType.replace(O,""))return b=false});b&&c.event.remove(this,a.origType,oa)}},beforeunload:{setup:function(a,b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};var Ca=s.removeEventListener?function(a,b,d){a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=
+a;this.type=a.type}else this.type=a;this.timeStamp=J();this[G]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=Z;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped=Z;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=Z;this.stopPropagation()},isDefaultPrevented:Y,isPropagationStopped:Y,
+isImmediatePropagationStopped:Y};var Da=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},Ea=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ea:Da,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ea:Da)}}});if(!c.support.submitBubbles)c.event.special.submit=
+{setup:function(){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length)return na("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13)return na("submit",this,arguments)})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};
+if(!c.support.changeBubbles){var da=/textarea|input|select/i,ea,Fa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",
+e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,
+"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,
+d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j<o;j++)c.event.add(this[j],d,i,f)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&
+!a.preventDefault)for(var d in a)this.unbind(d,a[d]);else{d=0;for(var f=this.length;d<f;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,f){return this.live(b,d,f,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},
+toggle:function(a){for(var b=arguments,d=1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(f){var e=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,e+1);f.preventDefault();return b[e].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var Ga={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,f,e,j){var i,o=0,k,n,r=j||this.selector,
+u=j?this:c(this.context);if(c.isFunction(f)){e=f;f=w}for(d=(d||"").split(" ");(i=d[o++])!=null;){j=O.exec(i);k="";if(j){k=j[0];i=i.replace(O,"")}if(i==="hover")d.push("mouseenter"+k,"mouseleave"+k);else{n=i;if(i==="focus"||i==="blur"){d.push(Ga[i]+k);i+=k}else i=(Ga[i]||i)+k;b==="live"?u.each(function(){c.event.add(this,pa(i,r),{data:f,selector:r,handler:e,origType:i,origHandler:e,preType:n})}):u.unbind(pa(i,r),e)}}return this}});c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),
+function(a,b){c.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});A.attachEvent&&!A.addEventListener&&A.attachEvent("onunload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});(function(){function a(g){for(var h="",l,m=0;g[m];m++){l=g[m];if(l.nodeType===3||l.nodeType===4)h+=l.nodeValue;else if(l.nodeType!==8)h+=a(l.childNodes)}return h}function b(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];
+if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1&&!p){t.sizcache=l;t.sizset=q}if(t.nodeName.toLowerCase()===h){y=t;break}t=t[g]}m[q]=y}}}function d(g,h,l,m,q,p){q=0;for(var v=m.length;q<v;q++){var t=m[q];if(t){t=t[g];for(var y=false;t;){if(t.sizcache===l){y=m[t.sizset];break}if(t.nodeType===1){if(!p){t.sizcache=l;t.sizset=q}if(typeof h!=="string"){if(t===h){y=true;break}}else if(k.filter(h,[t]).length>0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift();
+t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D||
+g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h<g.length;h++)g[h]===g[h-1]&&g.splice(h--,1)}return g};k.matches=function(g,h){return k(g,null,null,h)};k.find=function(g,h,l){var m,q;if(!g)return[];
+for(var p=0,v=n.order.length;p<v;p++){var t=n.order[p];if(q=n.leftMatch[t].exec(g)){var y=q[1];q.splice(1,1);if(y.substr(y.length-1)!=="\\"){q[1]=(q[1]||"").replace(/\\/g,"");m=n.find[t](q,h,l);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=h.getElementsByTagName("*"));return{set:m,expr:g}};k.filter=function(g,h,l,m){for(var q=g,p=[],v=h,t,y,S=h&&h[0]&&x(h[0]);g&&h.length;){for(var H in n.filter)if((t=n.leftMatch[H].exec(g))!=null&&t[2]){var M=n.filter[H],I,D;D=t[1];y=false;t.splice(1,1);if(D.substr(D.length-
+1)!=="\\"){if(v===p)p=[];if(n.preFilter[H])if(t=n.preFilter[H](t,v,l,p,m,S)){if(t===true)continue}else y=I=true;if(t)for(var U=0;(D=v[U])!=null;U++)if(D){I=M(D,t,U,v);var Ha=m^!!I;if(l&&I!=null)if(Ha)y=true;else v[U]=false;else if(Ha){p.push(D);y=true}}if(I!==w){l||(v=p);g=g.replace(n.match[H],"");if(!y)return[];break}}}if(g===q)if(y==null)k.error(g);else break;q=g}return v};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+CLASS:/\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},
+relative:{"+":function(g,h){var l=typeof h==="string",m=l&&!/\W/.test(h);l=l&&!m;if(m)h=h.toLowerCase();m=0;for(var q=g.length,p;m<q;m++)if(p=g[m]){for(;(p=p.previousSibling)&&p.nodeType!==1;);g[m]=l||p&&p.nodeName.toLowerCase()===h?p||false:p===h}l&&k.filter(h,g,true)},">":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m<q;m++){var p=g[m];if(p){l=p.parentNode;g[m]=l.nodeName.toLowerCase()===h?l:false}}}else{m=0;for(q=g.length;m<q;m++)if(p=g[m])g[m]=
+l?p.parentNode:p.parentNode===h;l&&k.filter(h,g,true)}},"":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("parentNode",h,m,g,p,l)},"~":function(g,h,l){var m=e++,q=d;if(typeof h==="string"&&!/\W/.test(h)){var p=h=h.toLowerCase();q=b}q("previousSibling",h,m,g,p,l)}},find:{ID:function(g,h,l){if(typeof h.getElementById!=="undefined"&&!l)return(g=h.getElementById(g[1]))?[g]:[]},NAME:function(g,h){if(typeof h.getElementsByName!=="undefined"){var l=[];
+h=h.getElementsByName(g[1]);for(var m=0,q=h.length;m<q;m++)h[m].getAttribute("name")===g[1]&&l.push(h[m]);return l.length===0?null:l}},TAG:function(g,h){return h.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,h,l,m,q,p){g=" "+g[1].replace(/\\/g,"")+" ";if(p)return g;p=0;for(var v;(v=h[p])!=null;p++)if(v)if(q^(v.className&&(" "+v.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},
+CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m,
+g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},
+text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},
+setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return h<l[3]-0},gt:function(g,h,l){return h>l[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h=
+h[3];l=0;for(m=h.length;l<m;l++)if(h[l]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+q)},CHILD:function(g,h){var l=h[1],m=g;switch(l){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(l==="first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":l=h[2];var q=h[3];if(l===1&&q===0)return true;h=h[0];var p=g.parentNode;if(p&&(p.sizcache!==h||!g.nodeIndex)){var v=0;for(m=p.firstChild;m;m=
+m.nextSibling)if(m.nodeType===1)m.nodeIndex=++v;p.sizcache=h}g=g.nodeIndex-q;return l===0?g===0:g%l===0&&g/l>=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m===
+"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g,
+h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l<m;l++)h.push(g[l]);else for(l=0;g[l];l++)h.push(g[l]);return h}}var B;if(s.documentElement.compareDocumentPosition)B=function(g,h){if(!g.compareDocumentPosition||
+!h.compareDocumentPosition){if(g==h)i=true;return g.compareDocumentPosition?-1:1}g=g.compareDocumentPosition(h)&4?-1:g===h?0:1;if(g===0)i=true;return g};else if("sourceIndex"in s.documentElement)B=function(g,h){if(!g.sourceIndex||!h.sourceIndex){if(g==h)i=true;return g.sourceIndex?-1:1}g=g.sourceIndex-h.sourceIndex;if(g===0)i=true;return g};else if(s.createRange)B=function(g,h){if(!g.ownerDocument||!h.ownerDocument){if(g==h)i=true;return g.ownerDocument?-1:1}var l=g.ownerDocument.createRange(),m=
+h.ownerDocument.createRange();l.setStart(g,0);l.setEnd(g,0);m.setStart(h,0);m.setEnd(h,0);g=l.compareBoundaryPoints(Range.START_TO_END,m);if(g===0)i=true;return g};(function(){var g=s.createElement("div"),h="script"+(new Date).getTime();g.innerHTML="<a name='"+h+"'/>";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&&
+q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML="<a href='#'></a>";
+if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="<p class='TEST'></p>";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}();
+(function(){var g=s.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}:
+function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q<p;q++)k(g,h[q],l);return k.filter(m,l)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=a;c.isXMLDoc=x;c.contains=E})();var eb=/Until$/,fb=/^(?:parents|prevUntil|prevAll)/,
+gb=/,/;R=Array.prototype.slice;var Ia=function(a,b,d){if(c.isFunction(b))return c.grep(a,function(e,j){return!!b.call(e,j,e)===d});else if(b.nodeType)return c.grep(a,function(e){return e===b===d});else if(typeof b==="string"){var f=c.grep(a,function(e){return e.nodeType===1});if(Ua.test(b))return c.filter(b,f,!d);else b=c.filter(b,f)}return c.grep(a,function(e){return c.inArray(e,b)>=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f<e;f++){d=b.length;
+c.find(a,this[f],b);if(f>0)for(var j=d;j<b.length;j++)for(var i=0;i<d;i++)if(b[i]===b[j]){b.splice(j--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,f=b.length;d<f;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(Ia(this,a,false),"not",a)},filter:function(a){return this.pushStack(Ia(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j=
+{},i;if(f&&a.length){e=0;for(var o=a.length;e<o;e++){i=a[e];j[i]||(j[i]=c.expr.match.POS.test(i)?c(i,b||this.context):i)}for(;f&&f.ownerDocument&&f!==b;){for(i in j){e=j[i];if(e.jquery?e.index(f)>-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a===
+"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",
+d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?
+a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType===
+1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/<tbody/i,jb=/<|&#?\w+;/,ta=/<script|<object|<embed|<option|<style/i,ua=/checked\s*(?:[^=]|=\s*.checked.)/i,Ma=function(a,b,d){return hb.test(d)?
+a:b+"></"+d+">"},F={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
+c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
+wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
+prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
+this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
+return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja,
+""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(f){this.empty().append(a)}}else c.isFunction(a)?this.each(function(e){var j=c(this),i=j.html();j.empty().append(function(){return a.call(this,e,i)})}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&
+this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),f=d.html();d.replaceWith(a.call(this,b,f))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){function f(u){return c.nodeName(u,"table")?u.getElementsByTagName("tbody")[0]||
+u.appendChild(u.ownerDocument.createElement("tbody")):u}var e,j,i=a[0],o=[],k;if(!c.support.checkClone&&arguments.length===3&&typeof i==="string"&&ua.test(i))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(i))return this.each(function(u){var z=c(this);a[0]=i.call(this,u,b?z.html():w);z.domManip(a,b,d)});if(this[0]){e=i&&i.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:sa(a,this,o);k=e.fragment;if(j=k.childNodes.length===
+1?(k=k.firstChild):k.firstChild){b=b&&c.nodeName(j,"tr");for(var n=0,r=this.length;n<r;n++)d.call(b?f(this[n],j):this[n],n>0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]);
+return this}else{e=0;for(var j=d.length;e<j;e++){var i=(e>0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["",
+""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]==="<table>"&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e=
+c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]?
+c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja=
+function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter=
+Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a,
+"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f=
+a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=
+a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=/<script(.|\s)*?\/script>/gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!==
+"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("<div />").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this},
+serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),
+function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,
+global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&&
+e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)?
+"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache===
+false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B=
+false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since",
+c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E||
+d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x);
+g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===
+1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b===
+"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional;
+if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");
+this[a].style.display=d||"";if(c.css(this[a],"display")==="none"){d=this[a].nodeName;var f;if(la[d])f=la[d];else{var e=c("<"+d+" />").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b){if(a||a===0)return this.animate(K("hide",3),a,b);else{a=0;for(b=this.length;a<b;a++){var d=c.data(this[a],"olddisplay");!d&&d!=="none"&&c.data(this[a],
+"olddisplay",c.css(this[a],"display"))}a=0;for(b=this.length;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b){var d=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||d?this.each(function(){var f=d?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(K("toggle",3),a,b);return this},fadeTo:function(a,b,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d)},
+animate:function(a,b,d,f){var e=c.speed(b,d,f);if(c.isEmptyObject(a))return this.each(e.complete);return this[e.queue===false?"each":"queue"](function(){var j=c.extend({},e),i,o=this.nodeType===1&&c(this).is(":hidden"),k=this;for(i in a){var n=i.replace(ia,ja);if(i!==n){a[n]=a[i];delete a[i];i=n}if(a[i]==="hide"&&o||a[i]==="show"&&!o)return j.complete.call(this);if((i==="height"||i==="width")&&this.style){j.display=c.css(this,"display");j.overflow=this.style.overflow}if(c.isArray(a[i])){(j.specialEasing=
+j.specialEasing||{})[i]=a[i][1];a[i]=a[i][0]}}if(j.overflow!=null)this.style.overflow="hidden";j.curAnim=c.extend({},a);c.each(a,function(r,u){var z=new c.fx(k,j,r);if(Ab.test(u))z[u==="toggle"?o?"show":"hide":u](a);else{var C=Bb.exec(u),B=z.cur(true)||0;if(C){u=parseFloat(C[2]);var E=C[3]||"px";if(E!=="px"){k.style[r]=(u||1)+E;B=(u||1)/z.cur(true)*B;k.style[r]=B+E}if(C[1])u=(C[1]==="-="?-1:1)*u+B;z.custom(B,u,E)}else z.custom(B,u,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);
+this.each(function(){for(var f=d.length-1;f>=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration===
+"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||
+c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;
+this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=
+this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem,
+e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||
+c.fx.stop()},stop:function(){clearInterval(W);W=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===b.elem}).length};c.fn.offset="getBoundingClientRect"in s.documentElement?
+function(a){var b=this[0];if(a)return this.each(function(e){c.offset.setOffset(this,a,e)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);var d=b.getBoundingClientRect(),f=b.ownerDocument;b=f.body;f=f.documentElement;return{top:d.top+(self.pageYOffset||c.support.boxModel&&f.scrollTop||b.scrollTop)-(f.clientTop||b.clientTop||0),left:d.left+(self.pageXOffset||c.support.boxModel&&f.scrollLeft||b.scrollLeft)-(f.clientLeft||b.clientLeft||0)}}:function(a){var b=
+this[0];if(a)return this.each(function(r){c.offset.setOffset(this,a,r)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,f=b,e=b.ownerDocument,j,i=e.documentElement,o=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;for(var k=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==o&&b!==i;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;j=e?e.getComputedStyle(b,null):b.currentStyle;
+k-=b.scrollTop;n-=b.scrollLeft;if(b===d){k+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(b.nodeName))){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=d;d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&j.overflow!=="visible"){k+=parseFloat(j.borderTopWidth)||0;n+=parseFloat(j.borderLeftWidth)||0}f=j}if(f.position==="relative"||f.position==="static"){k+=o.offsetTop;n+=o.offsetLeft}if(c.offset.supportsFixedPosition&&
+f.position==="fixed"){k+=Math.max(i.scrollTop,o.scrollTop);n+=Math.max(i.scrollLeft,o.scrollLeft)}return{top:k,left:n}};c.offset={initialize:function(){var a=s.body,b=s.createElement("div"),d,f,e,j=parseFloat(c.curCSS(a,"marginTop",true))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";
+a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b);
+c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a,
+d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top-
+f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset":
+"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in
+e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window);
--- /dev/null
+/*
+ Copyright (C) 2011 Pau Escrich <pau@dabax.net>
+ Contributors Lluis Esquerda <eskerda@gmail.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+ The full GNU General Public License is included in this distribution in
+ the file called "COPYING".
+*/
+
+
+/*
+ Table pooler is a function to easy call XHR poller.
+
+ new TablePooler(5,"/cgi-bin/bmx6-info", {'status':''}, "status_table", function(st){
+ var table = Array()
+ table.push(st.first,st.second)
+ return table
+ }
+ Parameters are:
+ polling_time: time between pollings
+ json_url: the json url to fetch the data
+ json_call: the json call
+ output_table_id: the table where javascript will put the data
+ callback_function: the function that will be executed each polling_time
+
+ The callback_function must return an array of arrays (matrix).
+ In the code st is the data obtained from the json call
+*/
+
+ function TablePooler (time, jsonurl, getparams, table_id, callback) {
+ this.table = document.getElementById(table_id);
+ this.callback = callback;
+ this.jsonurl = jsonurl;
+ this.getparams = getparams;
+ this.time = time;
+
+ this.clear = function(){
+ /* clear all rows */
+ while( this.table.rows.length > 1 ) this.table.deleteRow(1);
+ }
+ this.start = function(){
+ XHR.poll(this.time, this.jsonurl, this.getparams, function(x, st){
+ var data = this.callback(st);
+ var content, tr, td;
+ this.clear();
+ for (var i = 0; i < data.length; i++){
+ tr = this.table.insertRow(-1);
+ tr.className = 'cbi-section-table-row cbi-rowstyle-' + ((i % 2) + 1);
+
+ for (var j = 0; j < data[i].length; j++){
+ td = tr.insertCell(-1);
+ if (data[i][j].length == 2) {
+ td.colSpan = data[i][j][1];
+ content = data[i][j][0];
+ }
+ else content = data[i][j];
+ td.innerHTML = content;
+ }
+ }
+ }.bind(this));
+ }
+
+
+ this.start();
+ }
+
+
+
--- /dev/null
+/*
+ * Raphael 1.3.1 - JavaScript Vector Library
+ *
+ * Copyright (c) 2008 - 2009 Dmitry Baranovskiy (http://raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
+ */
+Raphael=(function(){var a=/[, ]+/,aO=/^(circle|rect|path|ellipse|text|image)$/,L=document,au=window,l={was:"Raphael" in au,is:au.Raphael},an=function(){if(an.is(arguments[0],"array")){var d=arguments[0],e=w[aW](an,d.splice(0,3+an.is(d[0],al))),S=e.set();for(var R=0,a0=d[m];R<a0;R++){var E=d[R]||{};aO.test(E.type)&&S[f](e[E.type]().attr(E));}return S;}return w[aW](an,arguments);},aT=function(){},aL="appendChild",aW="apply",aS="concat",at="",am=" ",z="split",F="click dblclick mousedown mousemove mouseout mouseover mouseup"[z](am),Q="hasOwnProperty",az="join",m="length",aY="prototype",aZ=String[aY].toLowerCase,ab=Math,g=ab.max,aI=ab.min,al="number",aA="toString",aw=Object[aY][aA],aQ={},aM=ab.pow,f="push",aU=/^(?=[\da-f]$)/,c=/^url\(['"]?([^\)]+)['"]?\)$/i,x=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgb\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|rgb\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\)|hs[bl]\(\s*([\d\.]+\s*,\s*[\d\.]+\s*,\s*[\d\.]+)\s*\)|hs[bl]\(\s*([\d\.]+%\s*,\s*[\d\.]+%\s*,\s*[\d\.]+%)\s*\))\s*$/i,O=ab.round,v="setAttribute",W=parseFloat,G=parseInt,aN=String[aY].toUpperCase,j={"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/",opacity:1,path:"M0,0",r:0,rotation:0,rx:0,ry:0,scale:"1 1",src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",translation:"0 0",width:0,x:0,y:0},Z={along:"along","clip-rect":"csv",cx:al,cy:al,fill:"colour","fill-opacity":al,"font-size":al,height:al,opacity:al,path:"path",r:al,rotation:"csv",rx:al,ry:al,scale:"csv",stroke:"colour","stroke-opacity":al,"stroke-width":al,translation:"csv",width:al,x:al,y:al},aP="replace";an.version="1.3.1";an.type=(au.SVGAngle||L.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML");if(an.type=="VML"){var ag=document.createElement("div");ag.innerHTML="<!--[if vml]><br><br><![endif]-->";if(ag.childNodes[m]!=2){return null;}}an.svg=!(an.vml=an.type=="VML");aT[aY]=an[aY];an._id=0;an._oid=0;an.fn={};an.is=function(e,d){d=aZ.call(d);return((d=="object"||d=="undefined")&&typeof e==d)||(e==null&&d=="null")||aZ.call(aw.call(e).slice(8,-1))==d;};an.setWindow=function(d){au=d;L=au.document;};var aD=function(e){if(an.vml){var d=/^\s+|\s+$/g;aD=aj(function(R){var S;R=(R+at)[aP](d,at);try{var a0=new ActiveXObject("htmlfile");a0.write("<body>");a0.close();S=a0.body;}catch(a2){S=createPopup().document.body;}var i=S.createTextRange();try{S.style.color=R;var a1=i.queryCommandValue("ForeColor");a1=((a1&255)<<16)|(a1&65280)|((a1&16711680)>>>16);return"#"+("000000"+a1[aA](16)).slice(-6);}catch(a2){return"none";}});}else{var E=L.createElement("i");E.title="Rapha\xebl Colour Picker";E.style.display="none";L.body[aL](E);aD=aj(function(i){E.style.color=i;return L.defaultView.getComputedStyle(E,at).getPropertyValue("color");});}return aD(e);};an.hsb2rgb=aj(function(a3,a1,a7){if(an.is(a3,"object")&&"h" in a3&&"s" in a3&&"b" in a3){a7=a3.b;a1=a3.s;a3=a3.h;}var R,S,a8;if(a7==0){return{r:0,g:0,b:0,hex:"#000"};}if(a3>1||a1>1||a7>1){a3/=255;a1/=255;a7/=255;}var a0=~~(a3*6),a4=(a3*6)-a0,E=a7*(1-a1),e=a7*(1-(a1*a4)),a9=a7*(1-(a1*(1-a4)));R=[a7,e,E,E,a9,a7,a7][a0];S=[a9,a7,a7,e,E,E,a9][a0];a8=[E,E,a9,a7,a7,e,E][a0];R*=255;S*=255;a8*=255;var a5={r:R,g:S,b:a8},d=(~~R)[aA](16),a2=(~~S)[aA](16),a6=(~~a8)[aA](16);d=d[aP](aU,"0");a2=a2[aP](aU,"0");a6=a6[aP](aU,"0");a5.hex="#"+d+a2+a6;return a5;},an);an.rgb2hsb=aj(function(d,e,a1){if(an.is(d,"object")&&"r" in d&&"g" in d&&"b" in d){a1=d.b;e=d.g;d=d.r;}if(an.is(d,"string")){var a3=an.getRGB(d);d=a3.r;e=a3.g;a1=a3.b;}if(d>1||e>1||a1>1){d/=255;e/=255;a1/=255;}var a0=g(d,e,a1),i=aI(d,e,a1),R,E,S=a0;if(i==a0){return{h:0,s:0,b:a0};}else{var a2=(a0-i);E=a2/a0;if(d==a0){R=(e-a1)/a2;}else{if(e==a0){R=2+((a1-d)/a2);}else{R=4+((d-e)/a2);}}R/=6;R<0&&R++;R>1&&R--;}return{h:R,s:E,b:S};},an);var aE=/,?([achlmqrstvxz]),?/gi;an._path2string=function(){return this.join(",")[aP](aE,"$1");};function aj(E,e,d){function i(){var R=Array[aY].slice.call(arguments,0),a0=R[az]("\u25ba"),S=i.cache=i.cache||{},a1=i.count=i.count||[];if(S[Q](a0)){return d?d(S[a0]):S[a0];}a1[m]>=1000&&delete S[a1.shift()];a1[f](a0);S[a0]=E[aW](e,R);return d?d(S[a0]):S[a0];}return i;}an.getRGB=aj(function(d){if(!d||!!((d=d+at).indexOf("-")+1)){return{r:-1,g:-1,b:-1,hex:"none",error:1};}if(d=="none"){return{r:-1,g:-1,b:-1,hex:"none"};}!(({hs:1,rg:1})[Q](d.substring(0,2))||d.charAt()=="#")&&(d=aD(d));var S,i,E,a2,a3,a0=d.match(x);if(a0){if(a0[2]){a2=G(a0[2].substring(5),16);E=G(a0[2].substring(3,5),16);i=G(a0[2].substring(1,3),16);}if(a0[3]){a2=G((a3=a0[3].charAt(3))+a3,16);E=G((a3=a0[3].charAt(2))+a3,16);i=G((a3=a0[3].charAt(1))+a3,16);}if(a0[4]){a0=a0[4][z](/\s*,\s*/);i=W(a0[0]);E=W(a0[1]);a2=W(a0[2]);}if(a0[5]){a0=a0[5][z](/\s*,\s*/);i=W(a0[0])*2.55;E=W(a0[1])*2.55;a2=W(a0[2])*2.55;}if(a0[6]){a0=a0[6][z](/\s*,\s*/);i=W(a0[0]);E=W(a0[1]);a2=W(a0[2]);return an.hsb2rgb(i,E,a2);}if(a0[7]){a0=a0[7][z](/\s*,\s*/);i=W(a0[0])*2.55;E=W(a0[1])*2.55;a2=W(a0[2])*2.55;return an.hsb2rgb(i,E,a2);}a0={r:i,g:E,b:a2};var e=(~~i)[aA](16),R=(~~E)[aA](16),a1=(~~a2)[aA](16);e=e[aP](aU,"0");R=R[aP](aU,"0");a1=a1[aP](aU,"0");a0.hex="#"+e+R+a1;return a0;}return{r:-1,g:-1,b:-1,hex:"none",error:1};},an);an.getColor=function(e){var i=this.getColor.start=this.getColor.start||{h:0,s:1,b:e||0.75},d=this.hsb2rgb(i.h,i.s,i.b);i.h+=0.075;if(i.h>1){i.h=0;i.s-=0.2;i.s<=0&&(this.getColor.start={h:0,s:1,b:i.b});}return d.hex;};an.getColor.reset=function(){delete this.start;};an.parsePathString=aj(function(d){if(!d){return null;}var i={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},e=[];if(an.is(d,"array")&&an.is(d[0],"array")){e=av(d);}if(!e[m]){(d+at)[aP](/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,function(R,E,a1){var a0=[],S=aZ.call(E);a1[aP](/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,function(a3,a2){a2&&a0[f](+a2);});while(a0[m]>=i[S]){e[f]([E][aS](a0.splice(0,i[S])));if(!i[S]){break;}}});}e[aA]=an._path2string;return e;});an.findDotsAtSegment=function(e,d,be,bc,a0,R,a2,a1,a8){var a6=1-a8,a5=aM(a6,3)*e+aM(a6,2)*3*a8*be+a6*3*a8*a8*a0+aM(a8,3)*a2,a3=aM(a6,3)*d+aM(a6,2)*3*a8*bc+a6*3*a8*a8*R+aM(a8,3)*a1,ba=e+2*a8*(be-e)+a8*a8*(a0-2*be+e),a9=d+2*a8*(bc-d)+a8*a8*(R-2*bc+d),bd=be+2*a8*(a0-be)+a8*a8*(a2-2*a0+be),bb=bc+2*a8*(R-bc)+a8*a8*(a1-2*R+bc),a7=(1-a8)*e+a8*be,a4=(1-a8)*d+a8*bc,E=(1-a8)*a0+a8*a2,i=(1-a8)*R+a8*a1,S=(90-ab.atan((ba-bd)/(a9-bb))*180/ab.PI);(ba>bd||a9<bb)&&(S+=180);return{x:a5,y:a3,m:{x:ba,y:a9},n:{x:bd,y:bb},start:{x:a7,y:a4},end:{x:E,y:i},alpha:S};};var U=aj(function(a5){if(!a5){return{x:0,y:0,width:0,height:0};}a5=H(a5);var a2=0,a1=0,R=[],e=[],E;for(var S=0,a4=a5[m];S<a4;S++){E=a5[S];if(E[0]=="M"){a2=E[1];a1=E[2];R[f](a2);e[f](a1);}else{var a0=aC(a2,a1,E[1],E[2],E[3],E[4],E[5],E[6]);R=R[aS](a0.min.x,a0.max.x);e=e[aS](a0.min.y,a0.max.y);a2=E[5];a1=E[6];}}var d=aI[aW](0,R),a3=aI[aW](0,e);return{x:d,y:a3,width:g[aW](0,R)-d,height:g[aW](0,e)-a3};}),av=function(a0){var E=[];if(!an.is(a0,"array")||!an.is(a0&&a0[0],"array")){a0=an.parsePathString(a0);}for(var e=0,R=a0[m];e<R;e++){E[e]=[];for(var d=0,S=a0[e][m];d<S;d++){E[e][d]=a0[e][d];}}E[aA]=an._path2string;return E;},ad=aj(function(R){if(!an.is(R,"array")||!an.is(R&&R[0],"array")){R=an.parsePathString(R);}var a4=[],a6=0,a5=0,a9=0,a8=0,E=0;if(R[0][0]=="M"){a6=R[0][1];a5=R[0][2];a9=a6;a8=a5;E++;a4[f](["M",a6,a5]);}for(var a1=E,ba=R[m];a1<ba;a1++){var d=a4[a1]=[],a7=R[a1];if(a7[0]!=aZ.call(a7[0])){d[0]=aZ.call(a7[0]);switch(d[0]){case"a":d[1]=a7[1];d[2]=a7[2];d[3]=a7[3];d[4]=a7[4];d[5]=a7[5];d[6]=+(a7[6]-a6).toFixed(3);d[7]=+(a7[7]-a5).toFixed(3);break;case"v":d[1]=+(a7[1]-a5).toFixed(3);break;case"m":a9=a7[1];a8=a7[2];default:for(var a0=1,a2=a7[m];a0<a2;a0++){d[a0]=+(a7[a0]-((a0%2)?a6:a5)).toFixed(3);}}}else{d=a4[a1]=[];if(a7[0]=="m"){a9=a7[1]+a6;a8=a7[2]+a5;}for(var S=0,e=a7[m];S<e;S++){a4[a1][S]=a7[S];}}var a3=a4[a1][m];switch(a4[a1][0]){case"z":a6=a9;a5=a8;break;case"h":a6+=+a4[a1][a3-1];break;case"v":a5+=+a4[a1][a3-1];break;default:a6+=+a4[a1][a3-2];a5+=+a4[a1][a3-1];}}a4[aA]=an._path2string;return a4;},0,av),r=aj(function(R){if(!an.is(R,"array")||!an.is(R&&R[0],"array")){R=an.parsePathString(R);}var a3=[],a5=0,a4=0,a8=0,a7=0,E=0;if(R[0][0]=="M"){a5=+R[0][1];a4=+R[0][2];a8=a5;a7=a4;E++;a3[0]=["M",a5,a4];}for(var a1=E,a9=R[m];a1<a9;a1++){var d=a3[a1]=[],a6=R[a1];if(a6[0]!=aN.call(a6[0])){d[0]=aN.call(a6[0]);switch(d[0]){case"A":d[1]=a6[1];d[2]=a6[2];d[3]=a6[3];d[4]=a6[4];d[5]=a6[5];d[6]=+(a6[6]+a5);d[7]=+(a6[7]+a4);break;case"V":d[1]=+a6[1]+a4;break;case"H":d[1]=+a6[1]+a5;break;case"M":a8=+a6[1]+a5;a7=+a6[2]+a4;default:for(var a0=1,a2=a6[m];a0<a2;a0++){d[a0]=+a6[a0]+((a0%2)?a5:a4);}}}else{for(var S=0,e=a6[m];S<e;S++){a3[a1][S]=a6[S];}}switch(d[0]){case"Z":a5=a8;a4=a7;break;case"H":a5=d[1];break;case"V":a4=d[1];break;default:a5=a3[a1][a3[a1][m]-2];a4=a3[a1][a3[a1][m]-1];}}a3[aA]=an._path2string;return a3;},null,av),aX=function(e,E,d,i){return[e,E,d,i,d,i];},aK=function(e,E,a0,R,d,i){var S=1/3,a1=2/3;return[S*e+a1*a0,S*E+a1*R,S*d+a1*a0,S*i+a1*R,d,i];},K=function(a9,bE,bi,bg,ba,a4,S,a8,bD,bb){var R=ab.PI,bf=R*120/180,d=R/180*(+ba||0),bm=[],bj,bA=aj(function(bF,bI,i){var bH=bF*ab.cos(i)-bI*ab.sin(i),bG=bF*ab.sin(i)+bI*ab.cos(i);return{x:bH,y:bG};});if(!bb){bj=bA(a9,bE,-d);a9=bj.x;bE=bj.y;bj=bA(a8,bD,-d);a8=bj.x;bD=bj.y;var e=ab.cos(R/180*ba),a6=ab.sin(R/180*ba),bo=(a9-a8)/2,bn=(bE-bD)/2;bi=g(bi,ab.abs(bo));bg=g(bg,ab.abs(bn));var by=(bo*bo)/(bi*bi)+(bn*bn)/(bg*bg);if(by>1){bi=ab.sqrt(by)*bi;bg=ab.sqrt(by)*bg;}var E=bi*bi,br=bg*bg,bt=(a4==S?-1:1)*ab.sqrt(ab.abs((E*br-E*bn*bn-br*bo*bo)/(E*bn*bn+br*bo*bo))),bd=bt*bi*bn/bg+(a9+a8)/2,bc=bt*-bg*bo/bi+(bE+bD)/2,a3=ab.asin(((bE-bc)/bg).toFixed(7)),a2=ab.asin(((bD-bc)/bg).toFixed(7));a3=a9<bd?R-a3:a3;a2=a8<bd?R-a2:a2;a3<0&&(a3=R*2+a3);a2<0&&(a2=R*2+a2);if(S&&a3>a2){a3=a3-R*2;}if(!S&&a2>a3){a2=a2-R*2;}}else{a3=bb[0];a2=bb[1];bd=bb[2];bc=bb[3];}var a7=a2-a3;if(ab.abs(a7)>bf){var be=a2,bh=a8,a5=bD;a2=a3+bf*(S&&a2>a3?1:-1);a8=bd+bi*ab.cos(a2);bD=bc+bg*ab.sin(a2);bm=K(a8,bD,bi,bg,ba,0,S,bh,a5,[a2,be,bd,bc]);}a7=a2-a3;var a1=ab.cos(a3),bC=ab.sin(a3),a0=ab.cos(a2),bB=ab.sin(a2),bp=ab.tan(a7/4),bs=4/3*bi*bp,bq=4/3*bg*bp,bz=[a9,bE],bx=[a9+bs*bC,bE-bq*a1],bw=[a8+bs*bB,bD-bq*a0],bu=[a8,bD];bx[0]=2*bz[0]-bx[0];bx[1]=2*bz[1]-bx[1];if(bb){return[bx,bw,bu][aS](bm);}else{bm=[bx,bw,bu][aS](bm)[az]()[z](",");var bk=[];for(var bv=0,bl=bm[m];bv<bl;bv++){bk[bv]=bv%2?bA(bm[bv-1],bm[bv],d).y:bA(bm[bv],bm[bv+1],d).x;}return bk;}},M=function(e,d,E,i,a2,a1,a0,S,a3){var R=1-a3;return{x:aM(R,3)*e+aM(R,2)*3*a3*E+R*3*a3*a3*a2+aM(a3,3)*a0,y:aM(R,3)*d+aM(R,2)*3*a3*i+R*3*a3*a3*a1+aM(a3,3)*S};},aC=aj(function(i,d,R,E,a9,a8,a5,a2){var a7=(a9-2*R+i)-(a5-2*a9+R),a4=2*(R-i)-2*(a9-R),a1=i-R,a0=(-a4+ab.sqrt(a4*a4-4*a7*a1))/2/a7,S=(-a4-ab.sqrt(a4*a4-4*a7*a1))/2/a7,a3=[d,a2],a6=[i,a5],e;ab.abs(a0)>1000000000000&&(a0=0.5);ab.abs(S)>1000000000000&&(S=0.5);if(a0>0&&a0<1){e=M(i,d,R,E,a9,a8,a5,a2,a0);a6[f](e.x);a3[f](e.y);}if(S>0&&S<1){e=M(i,d,R,E,a9,a8,a5,a2,S);a6[f](e.x);a3[f](e.y);}a7=(a8-2*E+d)-(a2-2*a8+E);a4=2*(E-d)-2*(a8-E);a1=d-E;a0=(-a4+ab.sqrt(a4*a4-4*a7*a1))/2/a7;S=(-a4-ab.sqrt(a4*a4-4*a7*a1))/2/a7;ab.abs(a0)>1000000000000&&(a0=0.5);ab.abs(S)>1000000000000&&(S=0.5);if(a0>0&&a0<1){e=M(i,d,R,E,a9,a8,a5,a2,a0);a6[f](e.x);a3[f](e.y);}if(S>0&&S<1){e=M(i,d,R,E,a9,a8,a5,a2,S);a6[f](e.x);a3[f](e.y);}return{min:{x:aI[aW](0,a6),y:aI[aW](0,a3)},max:{x:g[aW](0,a6),y:g[aW](0,a3)}};}),H=aj(function(a9,a4){var R=r(a9),a5=a4&&r(a4),a6={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},d={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},a0=function(ba,bb){var i,bc;if(!ba){return["C",bb.x,bb.y,bb.x,bb.y,bb.x,bb.y];}!(ba[0] in {T:1,Q:1})&&(bb.qx=bb.qy=null);switch(ba[0]){case"M":bb.X=ba[1];bb.Y=ba[2];break;case"A":ba=["C"][aS](K[aW](0,[bb.x,bb.y][aS](ba.slice(1))));break;case"S":i=bb.x+(bb.x-(bb.bx||bb.x));bc=bb.y+(bb.y-(bb.by||bb.y));ba=["C",i,bc][aS](ba.slice(1));break;case"T":bb.qx=bb.x+(bb.x-(bb.qx||bb.x));bb.qy=bb.y+(bb.y-(bb.qy||bb.y));ba=["C"][aS](aK(bb.x,bb.y,bb.qx,bb.qy,ba[1],ba[2]));break;case"Q":bb.qx=ba[1];bb.qy=ba[2];ba=["C"][aS](aK(bb.x,bb.y,ba[1],ba[2],ba[3],ba[4]));break;case"L":ba=["C"][aS](aX(bb.x,bb.y,ba[1],ba[2]));break;case"H":ba=["C"][aS](aX(bb.x,bb.y,ba[1],bb.y));break;case"V":ba=["C"][aS](aX(bb.x,bb.y,bb.x,ba[1]));break;case"Z":ba=["C"][aS](aX(bb.x,bb.y,bb.X,bb.Y));break;}return ba;},e=function(ba,bb){if(ba[bb][m]>7){ba[bb].shift();var bc=ba[bb];while(bc[m]){ba.splice(bb++,0,["C"][aS](bc.splice(0,6)));}ba.splice(bb,1);a7=g(R[m],a5&&a5[m]||0);}},E=function(be,bd,bb,ba,bc){if(be&&bd&&be[bc][0]=="M"&&bd[bc][0]!="M"){bd.splice(bc,0,["M",ba.x,ba.y]);bb.bx=0;bb.by=0;bb.x=be[bc][1];bb.y=be[bc][2];a7=g(R[m],a5&&a5[m]||0);}};for(var a2=0,a7=g(R[m],a5&&a5[m]||0);a2<a7;a2++){R[a2]=a0(R[a2],a6);e(R,a2);a5&&(a5[a2]=a0(a5[a2],d));a5&&e(a5,a2);E(R,a5,a6,d,a2);E(a5,R,d,a6,a2);var a1=R[a2],a8=a5&&a5[a2],S=a1[m],a3=a5&&a8[m];a6.x=a1[S-2];a6.y=a1[S-1];a6.bx=W(a1[S-4])||a6.x;a6.by=W(a1[S-3])||a6.y;d.bx=a5&&(W(a8[a3-4])||d.x);d.by=a5&&(W(a8[a3-3])||d.y);d.x=a5&&a8[a3-2];d.y=a5&&a8[a3-1];}return a5?[R,a5]:R;},null,av),p=aj(function(a4){var a3=[];for(var a0=0,a5=a4[m];a0<a5;a0++){var e={},a2=a4[a0].match(/^([^:]*):?([\d\.]*)/);e.color=an.getRGB(a2[1]);if(e.color.error){return null;}e.color=e.color.hex;a2[2]&&(e.offset=a2[2]+"%");a3[f](e);}for(var a0=1,a5=a3[m]-1;a0<a5;a0++){if(!a3[a0].offset){var E=W(a3[a0-1].offset||0),R=0;for(var S=a0+1;S<a5;S++){if(a3[S].offset){R=a3[S].offset;break;}}if(!R){R=100;S=a5;}R=W(R);var a1=(R-E)/(S-a0+1);for(;a0<S;a0++){E+=a1;a3[a0].offset=E+"%";}}}return a3;}),ao=function(){var i,e,R,E,d;if(an.is(arguments[0],"string")||an.is(arguments[0],"object")){if(an.is(arguments[0],"string")){i=L.getElementById(arguments[0]);}else{i=arguments[0];}if(i.tagName){if(arguments[1]==null){return{container:i,width:i.style.pixelWidth||i.offsetWidth,height:i.style.pixelHeight||i.offsetHeight};}else{return{container:i,width:arguments[1],height:arguments[2]};}}}else{if(an.is(arguments[0],al)&&arguments[m]>3){return{container:1,x:arguments[0],y:arguments[1],width:arguments[2],height:arguments[3]};}}},aG=function(d,i){var e=this;for(var E in i){if(i[Q](E)&&!(E in d)){switch(typeof i[E]){case"function":(function(R){d[E]=d===e?R:function(){return R[aW](e,arguments);};})(i[E]);break;case"object":d[E]=d[E]||{};aG.call(this,d[E],i[E]);break;default:d[E]=i[E];break;}}}},ak=function(d,e){d==e.top&&(e.top=d.prev);d==e.bottom&&(e.bottom=d.next);d.next&&(d.next.prev=d.prev);d.prev&&(d.prev.next=d.next);},Y=function(d,e){if(e.top===d){return;}ak(d,e);d.next=null;d.prev=e.top;e.top.next=d;e.top=d;},k=function(d,e){if(e.bottom===d){return;}ak(d,e);d.next=e.bottom;d.prev=null;e.bottom.prev=d;e.bottom=d;},A=function(e,d,i){ak(e,i);d==i.top&&(i.top=e);d.next&&(d.next.prev=e);e.next=d.next;e.prev=d;d.next=e;},aq=function(e,d,i){ak(e,i);d==i.bottom&&(i.bottom=e);d.prev&&(d.prev.next=e);e.prev=d.prev;d.prev=e;e.next=d;},s=function(d){return function(){throw new Error("Rapha\xebl: you are calling to method \u201c"+d+"\u201d of removed object");};},ar=/^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/;if(an.svg){aT[aY].svgns="http://www.w3.org/2000/svg";aT[aY].xlink="http://www.w3.org/1999/xlink";var O=function(d){return +d+(~~d===d)*0.5;},V=function(S){for(var e=0,E=S[m];e<E;e++){if(aZ.call(S[e][0])!="a"){for(var d=1,R=S[e][m];d<R;d++){S[e][d]=O(S[e][d]);}}else{S[e][6]=O(S[e][6]);S[e][7]=O(S[e][7]);}}return S;},aJ=function(i,d){if(d){for(var e in d){if(d[Q](e)){i[v](e,d[e]);}}}else{return L.createElementNS(aT[aY].svgns,i);}};an[aA]=function(){return"Your browser supports SVG.\nYou are running Rapha\xebl "+this.version;};var q=function(d,E){var e=aJ("path");E.canvas&&E.canvas[aL](e);var i=new ax(e,E);i.type="path";aa(i,{fill:"none",stroke:"#000",path:d});return i;};var b=function(E,a7,d){var a4="linear",a1=0.5,S=0.5,a9=E.style;a7=(a7+at)[aP](ar,function(bb,i,bc){a4="radial";if(i&&bc){a1=W(i);S=W(bc);var ba=((S>0.5)*2-1);aM(a1-0.5,2)+aM(S-0.5,2)>0.25&&(S=ab.sqrt(0.25-aM(a1-0.5,2))*ba+0.5)&&S!=0.5&&(S=S.toFixed(5)-0.00001*ba);}return at;});a7=a7[z](/\s*\-\s*/);if(a4=="linear"){var a0=a7.shift();a0=-W(a0);if(isNaN(a0)){return null;}var R=[0,0,ab.cos(a0*ab.PI/180),ab.sin(a0*ab.PI/180)],a6=1/(g(ab.abs(R[2]),ab.abs(R[3]))||1);R[2]*=a6;R[3]*=a6;if(R[2]<0){R[0]=-R[2];R[2]=0;}if(R[3]<0){R[1]=-R[3];R[3]=0;}}var a3=p(a7);if(!a3){return null;}var e=aJ(a4+"Gradient");e.id="r"+(an._id++)[aA](36);aJ(e,a4=="radial"?{fx:a1,fy:S}:{x1:R[0],y1:R[1],x2:R[2],y2:R[3]});d.defs[aL](e);for(var a2=0,a8=a3[m];a2<a8;a2++){var a5=aJ("stop");aJ(a5,{offset:a3[a2].offset?a3[a2].offset:!a2?"0%":"100%","stop-color":a3[a2].color||"#fff"});e[aL](a5);}aJ(E,{fill:"url(#"+e.id+")",opacity:1,"fill-opacity":1});a9.fill=at;a9.opacity=1;a9.fillOpacity=1;return 1;};var N=function(e){var d=e.getBBox();aJ(e.pattern,{patternTransform:an.format("translate({0},{1})",d.x,d.y)});};var aa=function(a6,bf){var a9={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},bb=a6.node,a7=a6.attrs,a3=a6.rotate(),S=function(bm,bl){bl=a9[aZ.call(bl)];if(bl){var bj=bm.attrs["stroke-width"]||"1",bh={round:bj,square:bj,butt:0}[bm.attrs["stroke-linecap"]||bf["stroke-linecap"]]||0,bk=[];var bi=bl[m];while(bi--){bk[bi]=bl[bi]*bj+((bi%2)?1:-1)*bh;}aJ(bb,{"stroke-dasharray":bk[az](",")});}};bf[Q]("rotation")&&(a3=bf.rotation);var a2=(a3+at)[z](a);if(!(a2.length-1)){a2=null;}else{a2[1]=+a2[1];a2[2]=+a2[2];}W(a3)&&a6.rotate(0,true);for(var ba in bf){if(bf[Q](ba)){if(!j[Q](ba)){continue;}var a8=bf[ba];a7[ba]=a8;switch(ba){case"rotation":a6.rotate(a8,true);break;case"href":case"title":case"target":var bd=bb.parentNode;if(aZ.call(bd.tagName)!="a"){var E=aJ("a");bd.insertBefore(E,bb);E[aL](bb);bd=E;}bd.setAttributeNS(a6.paper.xlink,ba,a8);break;case"cursor":bb.style.cursor=a8;break;case"clip-rect":var e=(a8+at)[z](a);if(e[m]==4){a6.clip&&a6.clip.parentNode.parentNode.removeChild(a6.clip.parentNode);var i=aJ("clipPath"),bc=aJ("rect");i.id="r"+(an._id++)[aA](36);aJ(bc,{x:e[0],y:e[1],width:e[2],height:e[3]});i[aL](bc);a6.paper.defs[aL](i);aJ(bb,{"clip-path":"url(#"+i.id+")"});a6.clip=bc;}if(!a8){var be=L.getElementById(bb.getAttribute("clip-path")[aP](/(^url\(#|\)$)/g,at));be&&be.parentNode.removeChild(be);aJ(bb,{"clip-path":at});delete a6.clip;}break;case"path":if(a8&&a6.type=="path"){a7.path=V(r(a8));aJ(bb,{d:a7.path});}break;case"width":bb[v](ba,a8);if(a7.fx){ba="x";a8=a7.x;}else{break;}case"x":if(a7.fx){a8=-a7.x-(a7.width||0);}case"rx":if(ba=="rx"&&a6.type=="rect"){break;}case"cx":a2&&(ba=="x"||ba=="cx")&&(a2[1]+=a8-a7[ba]);bb[v](ba,O(a8));a6.pattern&&N(a6);break;case"height":bb[v](ba,a8);if(a7.fy){ba="y";a8=a7.y;}else{break;}case"y":if(a7.fy){a8=-a7.y-(a7.height||0);}case"ry":if(ba=="ry"&&a6.type=="rect"){break;}case"cy":a2&&(ba=="y"||ba=="cy")&&(a2[2]+=a8-a7[ba]);bb[v](ba,O(a8));a6.pattern&&N(a6);break;case"r":if(a6.type=="rect"){aJ(bb,{rx:a8,ry:a8});}else{bb[v](ba,a8);}break;case"src":if(a6.type=="image"){bb.setAttributeNS(a6.paper.xlink,"href",a8);}break;case"stroke-width":bb.style.strokeWidth=a8;bb[v](ba,a8);if(a7["stroke-dasharray"]){S(a6,a7["stroke-dasharray"]);}break;case"stroke-dasharray":S(a6,a8);break;case"translation":var a0=(a8+at)[z](a);a0[0]=+a0[0]||0;a0[1]=+a0[1]||0;if(a2){a2[1]+=a0[0];a2[2]+=a0[1];}t.call(a6,a0[0],a0[1]);break;case"scale":var a0=(a8+at)[z](a);a6.scale(+a0[0]||1,+a0[1]||+a0[0]||1,+a0[2]||null,+a0[3]||null);break;case"fill":var R=(a8+at).match(c);if(R){var i=aJ("pattern"),a5=aJ("image");i.id="r"+(an._id++)[aA](36);aJ(i,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1});aJ(a5,{x:0,y:0});a5.setAttributeNS(a6.paper.xlink,"href",R[1]);i[aL](a5);var bg=L.createElement("img");bg.style.cssText="position:absolute;left:-9999em;top-9999em";bg.onload=function(){aJ(i,{width:this.offsetWidth,height:this.offsetHeight});aJ(a5,{width:this.offsetWidth,height:this.offsetHeight});L.body.removeChild(this);a6.paper.safari();};L.body[aL](bg);bg.src=R[1];a6.paper.defs[aL](i);bb.style.fill="url(#"+i.id+")";aJ(bb,{fill:"url(#"+i.id+")"});a6.pattern=i;a6.pattern&&N(a6);break;}if(!an.getRGB(a8).error){delete bf.gradient;delete a7.gradient;!an.is(a7.opacity,"undefined")&&an.is(bf.opacity,"undefined")&&aJ(bb,{opacity:a7.opacity});!an.is(a7["fill-opacity"],"undefined")&&an.is(bf["fill-opacity"],"undefined")&&aJ(bb,{"fill-opacity":a7["fill-opacity"]});}else{if((({circle:1,ellipse:1})[Q](a6.type)||(a8+at).charAt()!="r")&&b(bb,a8,a6.paper)){a7.gradient=a8;a7.fill="none";break;}}case"stroke":bb[v](ba,an.getRGB(a8).hex);break;case"gradient":(({circle:1,ellipse:1})[Q](a6.type)||(a8+at).charAt()!="r")&&b(bb,a8,a6.paper);break;case"opacity":case"fill-opacity":if(a7.gradient){var d=L.getElementById(bb.getAttribute("fill")[aP](/^url\(#|\)$/g,at));if(d){var a1=d.getElementsByTagName("stop");a1[a1[m]-1][v]("stop-opacity",a8);}break;}default:ba=="font-size"&&(a8=G(a8,10)+"px");var a4=ba[aP](/(\-.)/g,function(bh){return aN.call(bh.substring(1));});bb.style[a4]=a8;bb[v](ba,a8);break;}}}D(a6,bf);if(a2){a6.rotate(a2.join(am));}else{W(a3)&&a6.rotate(a3,true);}};var h=1.2;var D=function(d,R){if(d.type!="text"||!(R[Q]("text")||R[Q]("font")||R[Q]("font-size")||R[Q]("x")||R[Q]("y"))){return;}var a3=d.attrs,e=d.node,a5=e.firstChild?G(L.defaultView.getComputedStyle(e.firstChild,at).getPropertyValue("font-size"),10):10;if(R[Q]("text")){a3.text=R.text;while(e.firstChild){e.removeChild(e.firstChild);}var E=(R.text+at)[z]("\n");for(var S=0,a4=E[m];S<a4;S++){if(E[S]){var a1=aJ("tspan");S&&aJ(a1,{dy:a5*h,x:a3.x});a1[aL](L.createTextNode(E[S]));e[aL](a1);}}}else{var E=e.getElementsByTagName("tspan");for(var S=0,a4=E[m];S<a4;S++){S&&aJ(E[S],{dy:a5*h,x:a3.x});}}aJ(e,{y:a3.y});var a0=d.getBBox(),a2=a3.y-(a0.y+a0.height/2);a2&&isFinite(a2)&&aJ(e,{y:a3.y+a2});};var ax=function(e,d){var E=0,i=0;this[0]=e;this.id=an._oid++;this.node=e;e.raphael=this;this.paper=d;this.attrs=this.attrs||{};this.transformations=[];this._={tx:0,ty:0,rt:{deg:0,cx:0,cy:0},sx:1,sy:1};!d.bottom&&(d.bottom=this);this.prev=d.top;d.top&&(d.top.next=this);d.top=this;this.next=null;};ax[aY].rotate=function(e,d,E){if(this.removed){return this;}if(e==null){if(this._.rt.cx){return[this._.rt.deg,this._.rt.cx,this._.rt.cy][az](am);}return this._.rt.deg;}var i=this.getBBox();e=(e+at)[z](a);if(e[m]-1){d=W(e[1]);E=W(e[2]);}e=W(e[0]);if(d!=null){this._.rt.deg=e;}else{this._.rt.deg+=e;}(E==null)&&(d=null);this._.rt.cx=d;this._.rt.cy=E;d=d==null?i.x+i.width/2:d;E=E==null?i.y+i.height/2:E;if(this._.rt.deg){this.transformations[0]=an.format("rotate({0} {1} {2})",this._.rt.deg,d,E);this.clip&&aJ(this.clip,{transform:an.format("rotate({0} {1} {2})",-this._.rt.deg,d,E)});}else{this.transformations[0]=at;this.clip&&aJ(this.clip,{transform:at});}aJ(this.node,{transform:this.transformations[az](am)});return this;};ax[aY].hide=function(){!this.removed&&(this.node.style.display="none");return this;};ax[aY].show=function(){!this.removed&&(this.node.style.display="");return this;};ax[aY].remove=function(){if(this.removed){return;}ak(this,this.paper);this.node.parentNode.removeChild(this.node);for(var d in this){delete this[d];}this.removed=true;};ax[aY].getBBox=function(){if(this.removed){return this;}if(this.type=="path"){return U(this.attrs.path);}if(this.node.style.display=="none"){this.show();var E=true;}var a1={};try{a1=this.node.getBBox();}catch(S){}finally{a1=a1||{};}if(this.type=="text"){a1={x:a1.x,y:Infinity,width:0,height:0};for(var d=0,R=this.node.getNumberOfChars();d<R;d++){var a0=this.node.getExtentOfChar(d);(a0.y<a1.y)&&(a1.y=a0.y);(a0.y+a0.height-a1.y>a1.height)&&(a1.height=a0.y+a0.height-a1.y);(a0.x+a0.width-a1.x>a1.width)&&(a1.width=a0.x+a0.width-a1.x);}}E&&this.hide();return a1;};ax[aY].attr=function(){if(this.removed){return this;}if(arguments[m]==0){var R={};for(var E in this.attrs){if(this.attrs[Q](E)){R[E]=this.attrs[E];}}this._.rt.deg&&(R.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(R.scale=this.scale());R.gradient&&R.fill=="none"&&(R.fill=R.gradient)&&delete R.gradient;return R;}if(arguments[m]==1&&an.is(arguments[0],"string")){if(arguments[0]=="translation"){return t.call(this);}if(arguments[0]=="rotation"){return this.rotate();}if(arguments[0]=="scale"){return this.scale();}if(arguments[0]=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient){return this.attrs.gradient;}return this.attrs[arguments[0]];}if(arguments[m]==1&&an.is(arguments[0],"array")){var d={};for(var e in arguments[0]){if(arguments[0][Q](e)){d[arguments[0][e]]=this.attrs[arguments[0][e]];}}return d;}if(arguments[m]==2){var S={};S[arguments[0]]=arguments[1];aa(this,S);}else{if(arguments[m]==1&&an.is(arguments[0],"object")){aa(this,arguments[0]);}}return this;};ax[aY].toFront=function(){if(this.removed){return this;}this.node.parentNode[aL](this.node);var d=this.paper;d.top!=this&&Y(this,d);return this;};ax[aY].toBack=function(){if(this.removed){return this;}if(this.node.parentNode.firstChild!=this.node){this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild);k(this,this.paper);var d=this.paper;}return this;};ax[aY].insertAfter=function(d){if(this.removed){return this;}var e=d.node;if(e.nextSibling){e.parentNode.insertBefore(this.node,e.nextSibling);}else{e.parentNode[aL](this.node);}A(this,d,this.paper);return this;};ax[aY].insertBefore=function(d){if(this.removed){return this;}var e=d.node;e.parentNode.insertBefore(this.node,e);aq(this,d,this.paper);return this;};var P=function(e,d,S,R){d=O(d);S=O(S);var E=aJ("circle");e.canvas&&e.canvas[aL](E);var i=new ax(E,e);i.attrs={cx:d,cy:S,r:R,fill:"none",stroke:"#000"};i.type="circle";aJ(E,i.attrs);return i;};var aF=function(i,d,a1,e,S,a0){d=O(d);a1=O(a1);var R=aJ("rect");i.canvas&&i.canvas[aL](R);var E=new ax(R,i);E.attrs={x:d,y:a1,width:e,height:S,r:a0||0,rx:a0||0,ry:a0||0,fill:"none",stroke:"#000"};E.type="rect";aJ(R,E.attrs);return E;};var ai=function(e,d,a0,S,R){d=O(d);a0=O(a0);var E=aJ("ellipse");e.canvas&&e.canvas[aL](E);var i=new ax(E,e);i.attrs={cx:d,cy:a0,rx:S,ry:R,fill:"none",stroke:"#000"};i.type="ellipse";aJ(E,i.attrs);return i;};var o=function(i,a0,d,a1,e,S){var R=aJ("image");aJ(R,{x:d,y:a1,width:e,height:S,preserveAspectRatio:"none"});R.setAttributeNS(i.xlink,"href",a0);i.canvas&&i.canvas[aL](R);var E=new ax(R,i);E.attrs={x:d,y:a1,width:e,height:S,src:a0};E.type="image";return E;};var X=function(e,d,S,R){var E=aJ("text");aJ(E,{x:d,y:S,"text-anchor":"middle"});e.canvas&&e.canvas[aL](E);var i=new ax(E,e);i.attrs={x:d,y:S,"text-anchor":"middle",text:R,font:j.font,stroke:"none",fill:"#000"};i.type="text";aa(i,i.attrs);return i;};var aV=function(e,d){this.width=e||this.width;this.height=d||this.height;this.canvas[v]("width",this.width);this.canvas[v]("height",this.height);return this;};var w=function(){var E=ao[aW](null,arguments),i=E&&E.container,e=E.x,a0=E.y,R=E.width,d=E.height;if(!i){throw new Error("SVG container not found.");}var S=aJ("svg");R=R||512;d=d||342;aJ(S,{xmlns:"http://www.w3.org/2000/svg",version:1.1,width:R,height:d});if(i==1){S.style.cssText="position:absolute;left:"+e+"px;top:"+a0+"px";L.body[aL](S);}else{if(i.firstChild){i.insertBefore(S,i.firstChild);}else{i[aL](S);}}i=new aT;i.width=R;i.height=d;i.canvas=S;aG.call(i,i,an.fn);i.clear();return i;};aT[aY].clear=function(){var d=this.canvas;while(d.firstChild){d.removeChild(d.firstChild);}this.bottom=this.top=null;(this.desc=aJ("desc"))[aL](L.createTextNode("Created with Rapha\xebl"));d[aL](this.desc);d[aL](this.defs=aJ("defs"));};aT[aY].remove=function(){this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var d in this){this[d]=s(d);}};}if(an.vml){var aH=function(a8){var a5=/[ahqstv]/ig,a0=r;(a8+at).match(a5)&&(a0=H);a5=/[clmz]/g;if(a0==r&&!(a8+at).match(a5)){var e={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},R=/([clmz]),?([^clmz]*)/gi,S=/-?[^,\s-]+/g;var a4=(a8+at)[aP](R,function(a9,bb,i){var ba=[];i[aP](S,function(bc){ba[f](O(bc));});return e[bb]+ba;});return a4;}var a6=a0(a8),E,a4=[],d;for(var a2=0,a7=a6[m];a2<a7;a2++){E=a6[a2];d=aZ.call(a6[a2][0]);d=="z"&&(d="x");for(var a1=1,a3=E[m];a1<a3;a1++){d+=O(E[a1])+(a1!=a3-1?",":at);}a4[f](d);}return a4[az](am);};an[aA]=function(){return"Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl "+this.version;};var q=function(d,S){var E=ah("group");E.style.cssText="position:absolute;left:0;top:0;width:"+S.width+"px;height:"+S.height+"px";E.coordsize=S.coordsize;E.coordorigin=S.coordorigin;var i=ah("shape"),e=i.style;e.width=S.width+"px";e.height=S.height+"px";i.coordsize=this.coordsize;i.coordorigin=this.coordorigin;E[aL](i);var R=new ax(i,E,S);R.isAbsolute=true;R.type="path";R.path=[];R.Path=at;d&&aa(R,{fill:"none",stroke:"#000",path:d});S.canvas[aL](E);return R;};var aa=function(a3,a8){a3.attrs=a3.attrs||{};var a6=a3.node,a9=a3.attrs,a0=a6.style,E,bd=a3;for(var a1 in a8){if(a8[Q](a1)){a9[a1]=a8[a1];}}a8.href&&(a6.href=a8.href);a8.title&&(a6.title=a8.title);a8.target&&(a6.target=a8.target);a8.cursor&&(a0.cursor=a8.cursor);if(a8.path&&a3.type=="path"){a9.path=a8.path;a6.path=aH(a9.path);}if(a8.rotation!=null){a3.rotate(a8.rotation,true);}if(a8.translation){E=(a8.translation+at)[z](a);t.call(a3,E[0],E[1]);if(a3._.rt.cx!=null){a3._.rt.cx+=+E[0];a3._.rt.cy+=+E[1];a3.setBox(a3.attrs,E[0],E[1]);}}if(a8.scale){E=(a8.scale+at)[z](a);a3.scale(+E[0]||1,+E[1]||+E[0]||1,+E[2]||null,+E[3]||null);}if("clip-rect" in a8){var d=(a8["clip-rect"]+at)[z](a);if(d[m]==4){d[2]=+d[2]+(+d[0]);d[3]=+d[3]+(+d[1]);var a2=a6.clipRect||L.createElement("div"),bc=a2.style,S=a6.parentNode;bc.clip=an.format("rect({1}px {2}px {3}px {0}px)",d);if(!a6.clipRect){bc.position="absolute";bc.top=0;bc.left=0;bc.width=a3.paper.width+"px";bc.height=a3.paper.height+"px";S.parentNode.insertBefore(a2,S);a2[aL](S);a6.clipRect=a2;}}if(!a8["clip-rect"]){a6.clipRect&&(a6.clipRect.style.clip=at);}}if(a3.type=="image"&&a8.src){a6.src=a8.src;}if(a3.type=="image"&&a8.opacity){a6.filterOpacity=" progid:DXImageTransform.Microsoft.Alpha(opacity="+(a8.opacity*100)+")";a0.filter=(a6.filterMatrix||at)+(a6.filterOpacity||at);}a8.font&&(a0.font=a8.font);a8["font-family"]&&(a0.fontFamily='"'+a8["font-family"][z](",")[0][aP](/^['"]+|['"]+$/g,at)+'"');a8["font-size"]&&(a0.fontSize=a8["font-size"]);a8["font-weight"]&&(a0.fontWeight=a8["font-weight"]);a8["font-style"]&&(a0.fontStyle=a8["font-style"]);if(a8.opacity!=null||a8["stroke-width"]!=null||a8.fill!=null||a8.stroke!=null||a8["stroke-width"]!=null||a8["stroke-opacity"]!=null||a8["fill-opacity"]!=null||a8["stroke-dasharray"]!=null||a8["stroke-miterlimit"]!=null||a8["stroke-linejoin"]!=null||a8["stroke-linecap"]!=null){a6=a3.shape||a6;var a7=(a6.getElementsByTagName("fill")&&a6.getElementsByTagName("fill")[0]),ba=false;!a7&&(ba=a7=ah("fill"));if("fill-opacity" in a8||"opacity" in a8){var e=((+a9["fill-opacity"]+1||2)-1)*((+a9.opacity+1||2)-1);e<0&&(e=0);e>1&&(e=1);a7.opacity=e;}a8.fill&&(a7.on=true);if(a7.on==null||a8.fill=="none"){a7.on=false;}if(a7.on&&a8.fill){var i=a8.fill.match(c);if(i){a7.src=i[1];a7.type="tile";}else{a7.color=an.getRGB(a8.fill).hex;a7.src=at;a7.type="solid";if(an.getRGB(a8.fill).error&&(bd.type in {circle:1,ellipse:1}||(a8.fill+at).charAt()!="r")&&b(bd,a8.fill)){a9.fill="none";a9.gradient=a8.fill;}}}ba&&a6[aL](a7);var R=(a6.getElementsByTagName("stroke")&&a6.getElementsByTagName("stroke")[0]),bb=false;!R&&(bb=R=ah("stroke"));if((a8.stroke&&a8.stroke!="none")||a8["stroke-width"]||a8["stroke-opacity"]!=null||a8["stroke-dasharray"]||a8["stroke-miterlimit"]||a8["stroke-linejoin"]||a8["stroke-linecap"]){R.on=true;}(a8.stroke=="none"||R.on==null||a8.stroke==0||a8["stroke-width"]==0)&&(R.on=false);R.on&&a8.stroke&&(R.color=an.getRGB(a8.stroke).hex);var e=((+a9["stroke-opacity"]+1||2)-1)*((+a9.opacity+1||2)-1),a4=(W(a8["stroke-width"])||1)*0.75;e<0&&(e=0);e>1&&(e=1);a8["stroke-width"]==null&&(a4=a9["stroke-width"]);a8["stroke-width"]&&(R.weight=a4);a4&&a4<1&&(e*=a4)&&(R.weight=1);R.opacity=e;a8["stroke-linejoin"]&&(R.joinstyle=a8["stroke-linejoin"]||"miter");R.miterlimit=a8["stroke-miterlimit"]||8;a8["stroke-linecap"]&&(R.endcap=a8["stroke-linecap"]=="butt"?"flat":a8["stroke-linecap"]=="square"?"square":"round");if(a8["stroke-dasharray"]){var a5={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};R.dashstyle=a5[Q](a8["stroke-dasharray"])?a5[a8["stroke-dasharray"]]:at;}bb&&a6[aL](R);}if(bd.type=="text"){var a0=bd.paper.span.style;a9.font&&(a0.font=a9.font);a9["font-family"]&&(a0.fontFamily=a9["font-family"]);a9["font-size"]&&(a0.fontSize=a9["font-size"]);a9["font-weight"]&&(a0.fontWeight=a9["font-weight"]);a9["font-style"]&&(a0.fontStyle=a9["font-style"]);bd.node.string&&(bd.paper.span.innerHTML=(bd.node.string+at)[aP](/</g,"<")[aP](/&/g,"&")[aP](/\n/g,"<br>"));bd.W=a9.w=bd.paper.span.offsetWidth;bd.H=a9.h=bd.paper.span.offsetHeight;bd.X=a9.x;bd.Y=a9.y+O(bd.H/2);switch(a9["text-anchor"]){case"start":bd.node.style["v-text-align"]="left";bd.bbx=O(bd.W/2);break;case"end":bd.node.style["v-text-align"]="right";bd.bbx=-O(bd.W/2);break;default:bd.node.style["v-text-align"]="center";break;}}};var b=function(d,a1){d.attrs=d.attrs||{};var a2=d.attrs,a4=d.node.getElementsByTagName("fill"),S="linear",a0=".5 .5";d.attrs.gradient=a1;a1=(a1+at)[aP](ar,function(a6,a7,i){S="radial";if(a7&&i){a7=W(a7);i=W(i);aM(a7-0.5,2)+aM(i-0.5,2)>0.25&&(i=ab.sqrt(0.25-aM(a7-0.5,2))*((i>0.5)*2-1)+0.5);a0=a7+am+i;}return at;});a1=a1[z](/\s*\-\s*/);if(S=="linear"){var e=a1.shift();e=-W(e);if(isNaN(e)){return null;}}var R=p(a1);if(!R){return null;}d=d.shape||d.node;a4=a4[0]||ah("fill");if(R[m]){a4.on=true;a4.method="none";a4.type=(S=="radial")?"gradientradial":"gradient";a4.color=R[0].color;a4.color2=R[R[m]-1].color;var a5=[];for(var E=0,a3=R[m];E<a3;E++){R[E].offset&&a5[f](R[E].offset+am+R[E].color);}a4.colors&&(a4.colors.value=a5[m]?a5[az](","):"0% "+a4.color);if(S=="radial"){a4.focus="100%";a4.focussize=a0;a4.focusposition=a0;}else{a4.angle=(270-e)%360;}}return 1;};var ax=function(R,a0,d){var S=0,i=0,e=0,E=1;this[0]=R;this.id=an._oid++;this.node=R;R.raphael=this;this.X=0;this.Y=0;this.attrs={};this.Group=a0;this.paper=d;this._={tx:0,ty:0,rt:{deg:0},sx:1,sy:1};!d.bottom&&(d.bottom=this);this.prev=d.top;d.top&&(d.top.next=this);d.top=this;this.next=null;};ax[aY].rotate=function(e,d,i){if(this.removed){return this;}if(e==null){if(this._.rt.cx){return[this._.rt.deg,this._.rt.cx,this._.rt.cy][az](am);}return this._.rt.deg;}e=(e+at)[z](a);if(e[m]-1){d=W(e[1]);i=W(e[2]);}e=W(e[0]);if(d!=null){this._.rt.deg=e;}else{this._.rt.deg+=e;}i==null&&(d=null);this._.rt.cx=d;this._.rt.cy=i;this.setBox(this.attrs,d,i);this.Group.style.rotation=this._.rt.deg;return this;};ax[aY].setBox=function(bb,e,d){if(this.removed){return this;}var a5=this.Group.style,R=(this.shape&&this.shape.style)||this.node.style;bb=bb||{};for(var a9 in bb){if(bb[Q](a9)){this.attrs[a9]=bb[a9];}}e=e||this._.rt.cx;d=d||this._.rt.cy;var a7=this.attrs,a1,a0,a2,ba;switch(this.type){case"circle":a1=a7.cx-a7.r;a0=a7.cy-a7.r;a2=ba=a7.r*2;break;case"ellipse":a1=a7.cx-a7.rx;a0=a7.cy-a7.ry;a2=a7.rx*2;ba=a7.ry*2;break;case"rect":case"image":a1=+a7.x;a0=+a7.y;a2=a7.width||0;ba=a7.height||0;break;case"text":this.textpath.v=["m",O(a7.x),", ",O(a7.y-2),"l",O(a7.x)+1,", ",O(a7.y-2)][az](at);a1=a7.x-O(this.W/2);a0=a7.y-this.H/2;a2=this.W;ba=this.H;break;case"path":if(!this.attrs.path){a1=0;a0=0;a2=this.paper.width;ba=this.paper.height;}else{var a8=U(this.attrs.path);a1=a8.x;a0=a8.y;a2=a8.width;ba=a8.height;}break;default:a1=0;a0=0;a2=this.paper.width;ba=this.paper.height;break;}e=(e==null)?a1+a2/2:e;d=(d==null)?a0+ba/2:d;var E=e-this.paper.width/2,a4=d-this.paper.height/2;if(this.type=="path"||this.type=="text"){(a5.left!=E+"px")&&(a5.left=E+"px");(a5.top!=a4+"px")&&(a5.top=a4+"px");this.X=this.type=="text"?a1:-E;this.Y=this.type=="text"?a0:-a4;this.W=a2;this.H=ba;(R.left!=-E+"px")&&(R.left=-E+"px");(R.top!=-a4+"px")&&(R.top=-a4+"px");}else{(a5.left!=E+"px")&&(a5.left=E+"px");(a5.top!=a4+"px")&&(a5.top=a4+"px");this.X=a1;this.Y=a0;this.W=a2;this.H=ba;(a5.width!=this.paper.width+"px")&&(a5.width=this.paper.width+"px");(a5.height!=this.paper.height+"px")&&(a5.height=this.paper.height+"px");(R.left!=a1-E+"px")&&(R.left=a1-E+"px");(R.top!=a0-a4+"px")&&(R.top=a0-a4+"px");(R.width!=a2+"px")&&(R.width=a2+"px");(R.height!=ba+"px")&&(R.height=ba+"px");var S=(+bb.r||0)/aI(a2,ba);if(this.type=="rect"&&this.arcsize.toFixed(4)!=S.toFixed(4)&&(S||this.arcsize)){var a6=ah("roundrect"),bc={},a9=0,a3=this.events&&this.events[m];a6.arcsize=S;a6.raphael=this;this.Group[aL](a6);this.Group.removeChild(this.node);this[0]=this.node=a6;this.arcsize=S;for(var a9 in a7){bc[a9]=a7[a9];}delete bc.scale;this.attr(bc);if(this.events){for(;a9<a3;a9++){this.events[a9].unbind=ae(this.node,this.events[a9].name,this.events[a9].f,this);}}}}};ax[aY].hide=function(){!this.removed&&(this.Group.style.display="none");return this;};ax[aY].show=function(){!this.removed&&(this.Group.style.display="block");return this;};ax[aY].getBBox=function(){if(this.removed){return this;}if(this.type=="path"){return U(this.attrs.path);}return{x:this.X+(this.bbx||0),y:this.Y,width:this.W,height:this.H};};ax[aY].remove=function(){if(this.removed){return;}ak(this,this.paper);this.node.parentNode.removeChild(this.node);this.Group.parentNode.removeChild(this.Group);this.shape&&this.shape.parentNode.removeChild(this.shape);for(var d in this){delete this[d];}this.removed=true;};ax[aY].attr=function(){if(this.removed){return this;}if(arguments[m]==0){var E={};for(var e in this.attrs){if(this.attrs[Q](e)){E[e]=this.attrs[e];}}this._.rt.deg&&(E.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(E.scale=this.scale());E.gradient&&E.fill=="none"&&(E.fill=E.gradient)&&delete E.gradient;return E;}if(arguments[m]==1&&an.is(arguments[0],"string")){if(arguments[0]=="translation"){return t.call(this);}if(arguments[0]=="rotation"){return this.rotate();}if(arguments[0]=="scale"){return this.scale();}if(arguments[0]=="fill"&&this.attrs.fill=="none"&&this.attrs.gradient){return this.attrs.gradient;}return this.attrs[arguments[0]];}if(this.attrs&&arguments[m]==1&&an.is(arguments[0],"array")){var d={};for(var e=0,R=arguments[0][m];e<R;e++){d[arguments[0][e]]=this.attrs[arguments[0][e]];}return d;}var S;if(arguments[m]==2){S={};S[arguments[0]]=arguments[1];}arguments[m]==1&&an.is(arguments[0],"object")&&(S=arguments[0]);if(S){if(S.text&&this.type=="text"){this.node.string=S.text;}aa(this,S);if(S.gradient&&(({circle:1,ellipse:1})[Q](this.type)||(S.gradient+at).charAt()!="r")){b(this,S.gradient);}(this.type!="path"||this._.rt.deg)&&this.setBox(this.attrs);}return this;};ax[aY].toFront=function(){!this.removed&&this.Group.parentNode[aL](this.Group);this.paper.top!=this&&Y(this,this.paper);return this;};ax[aY].toBack=function(){if(this.removed){return this;}if(this.Group.parentNode.firstChild!=this.Group){this.Group.parentNode.insertBefore(this.Group,this.Group.parentNode.firstChild);k(this,this.paper);}return this;};ax[aY].insertAfter=function(d){if(this.removed){return this;}if(d.Group.nextSibling){d.Group.parentNode.insertBefore(this.Group,d.Group.nextSibling);}else{d.Group.parentNode[aL](this.Group);}A(this,d,this.paper);return this;};ax[aY].insertBefore=function(d){if(this.removed){return this;}d.Group.parentNode.insertBefore(this.Group,d.Group);aq(this,d,this.paper);return this;};var P=function(e,d,a1,S){var R=ah("group"),a0=ah("oval"),i=a0.style;R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=e.coordsize;R.coordorigin=e.coordorigin;R[aL](a0);var E=new ax(a0,R,e);E.type="circle";aa(E,{stroke:"#000",fill:"none"});E.attrs.cx=d;E.attrs.cy=a1;E.attrs.r=S;E.setBox({x:d-S,y:a1-S,width:S*2,height:S*2});e.canvas[aL](R);return E;},aF=function(e,a1,a0,a2,E,d){var R=ah("group"),i=ah("roundrect"),a3=(+d||0)/(aI(a2,E));R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=e.coordsize;R.coordorigin=e.coordorigin;R[aL](i);i.arcsize=a3;var S=new ax(i,R,e);S.type="rect";aa(S,{stroke:"#000"});S.arcsize=a3;S.setBox({x:a1,y:a0,width:a2,height:E,r:d});e.canvas[aL](R);return S;},ai=function(d,a2,a1,i,e){var R=ah("group"),E=ah("oval"),a0=E.style;R.style.cssText="position:absolute;left:0;top:0;width:"+d.width+"px;height:"+d.height+"px";R.coordsize=d.coordsize;R.coordorigin=d.coordorigin;R[aL](E);var S=new ax(E,R,d);S.type="ellipse";aa(S,{stroke:"#000"});S.attrs.cx=a2;S.attrs.cy=a1;S.attrs.rx=i;S.attrs.ry=e;S.setBox({x:a2-i,y:a1-e,width:i*2,height:e*2});d.canvas[aL](R);return S;},o=function(e,d,a2,a1,a3,E){var R=ah("group"),i=ah("image"),a0=i.style;R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=e.coordsize;R.coordorigin=e.coordorigin;i.src=d;R[aL](i);var S=new ax(i,R,e);S.type="image";S.attrs.src=d;S.attrs.x=a2;S.attrs.y=a1;S.attrs.w=a3;S.attrs.h=E;S.setBox({x:a2,y:a1,width:a3,height:E});e.canvas[aL](R);return S;},X=function(e,a2,a1,a3){var R=ah("group"),E=ah("shape"),a0=E.style,a4=ah("path"),d=a4.style,i=ah("textpath");R.style.cssText="position:absolute;left:0;top:0;width:"+e.width+"px;height:"+e.height+"px";R.coordsize=e.coordsize;R.coordorigin=e.coordorigin;a4.v=an.format("m{0},{1}l{2},{1}",O(a2),O(a1),O(a2)+1);a4.textpathok=true;a0.width=e.width;a0.height=e.height;i.string=a3+at;i.on=true;E[aL](i);E[aL](a4);R[aL](E);var S=new ax(i,R,e);S.shape=E;S.textpath=a4;S.type="text";S.attrs.text=a3;S.attrs.x=a2;S.attrs.y=a1;S.attrs.w=1;S.attrs.h=1;aa(S,{font:j.font,stroke:"none",fill:"#000"});S.setBox();e.canvas[aL](R);return S;},aV=function(i,d){var e=this.canvas.style;i==+i&&(i+="px");d==+d&&(d+="px");e.width=i;e.height=d;e.clip="rect(0 "+i+" "+d+" 0)";return this;},ah;L.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!L.namespaces.rvml&&L.namespaces.add("rvml","urn:schemas-microsoft-com:vml");ah=function(d){return L.createElement("<rvml:"+d+' class="rvml">');};}catch(af){ah=function(d){return L.createElement("<"+d+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');};}var w=function(){var i=ao[aW](null,arguments),d=i.container,a2=i.height,a3,e=i.width,a1=i.x,a0=i.y;if(!d){throw new Error("VML container not found.");}var R=new aT,S=R.canvas=L.createElement("div"),E=S.style;e=e||512;a2=a2||342;e==+e&&(e+="px");a2==+a2&&(a2+="px");R.width=1000;R.height=1000;R.coordsize="1000 1000";R.coordorigin="0 0";R.span=L.createElement("span");R.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";S[aL](R.span);E.cssText=an.format("width:{0};height:{1};position:absolute;clip:rect(0 {0} {1} 0);overflow:hidden",e,a2);if(d==1){L.body[aL](S);E.left=a1+"px";E.top=a0+"px";}else{d.style.width=e;d.style.height=a2;if(d.firstChild){d.insertBefore(S,d.firstChild);}else{d[aL](S);}}aG.call(R,R,an.fn);return R;};aT[aY].clear=function(){this.canvas.innerHTML=at;this.span=L.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[aL](this.span);this.bottom=this.top=null;};aT[aY].remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var d in this){this[d]=s(d);}};}if((/^Apple|^Google/).test(navigator.vendor)&&!(navigator.userAgent.indexOf("Version/4.0")+1)){aT[aY].safari=function(){var d=this.rect(-99,-99,this.width+99,this.height+99);setTimeout(function(){d.remove();});};}else{aT[aY].safari=function(){};}var ae=(function(){if(L.addEventListener){return function(R,i,e,d){var E=function(S){return e.call(d,S);};R.addEventListener(i,E,false);return function(){R.removeEventListener(i,E,false);return true;};};}else{if(L.attachEvent){return function(S,E,i,e){var R=function(a0){return i.call(e,a0||au.event);};S.attachEvent("on"+E,R);var d=function(){S.detachEvent("on"+E,R);return true;};return d;};}}})();for(var ac=F[m];ac--;){(function(d){ax[aY][d]=function(e){if(an.is(e,"function")){this.events=this.events||[];this.events.push({name:d,f:e,unbind:ae(this.shape||this.node,d,e,this)});}return this;};ax[aY]["un"+d]=function(E){var i=this.events,e=i[m];while(e--){if(i[e].name==d&&i[e].f==E){i[e].unbind();i.splice(e,1);!i.length&&delete this.events;return this;}}return this;};})(F[ac]);}ax[aY].hover=function(e,d){return this.mouseover(e).mouseout(d);};ax[aY].unhover=function(e,d){return this.unmouseover(e).unmouseout(d);};aT[aY].circle=function(d,i,e){return P(this,d||0,i||0,e||0);};aT[aY].rect=function(d,R,e,i,E){return aF(this,d||0,R||0,e||0,i||0,E||0);};aT[aY].ellipse=function(d,E,i,e){return ai(this,d||0,E||0,i||0,e||0);};aT[aY].path=function(d){d&&!an.is(d,"string")&&!an.is(d[0],"array")&&(d+=at);return q(an.format[aW](an,arguments),this);};aT[aY].image=function(E,d,R,e,i){return o(this,E||"about:blank",d||0,R||0,e||0,i||0);};aT[aY].text=function(d,i,e){return X(this,d||0,i||0,e||at);};aT[aY].set=function(d){arguments[m]>1&&(d=Array[aY].splice.call(arguments,0,arguments[m]));return new T(d);};aT[aY].setSize=aV;aT[aY].top=aT[aY].bottom=null;aT[aY].raphael=an;function u(){return this.x+am+this.y;}ax[aY].scale=function(a6,a5,E,e){if(a6==null&&a5==null){return{x:this._.sx,y:this._.sy,toString:u};}a5=a5||a6;!+a5&&(a5=a6);var ba,a8,a9,a7,bm=this.attrs;if(a6!=0){var a4=this.getBBox(),a1=a4.x+a4.width/2,R=a4.y+a4.height/2,bl=a6/this._.sx,bk=a5/this._.sy;E=(+E||E==0)?E:a1;e=(+e||e==0)?e:R;var a3=~~(a6/ab.abs(a6)),a0=~~(a5/ab.abs(a5)),be=this.node.style,bo=E+(a1-E)*bl,bn=e+(R-e)*bk;switch(this.type){case"rect":case"image":var a2=bm.width*a3*bl,bd=bm.height*a0*bk;this.attr({height:bd,r:bm.r*aI(a3*bl,a0*bk),width:a2,x:bo-a2/2,y:bn-bd/2});break;case"circle":case"ellipse":this.attr({rx:bm.rx*a3*bl,ry:bm.ry*a0*bk,r:bm.r*aI(a3*bl,a0*bk),cx:bo,cy:bn});break;case"path":var bg=ad(bm.path),bh=true;for(var bj=0,bc=bg[m];bj<bc;bj++){var bf=bg[bj],bi,S=aN.call(bf[0]);if(S=="M"&&bh){continue;}else{bh=false;}if(S=="A"){bf[bg[bj][m]-2]*=bl;bf[bg[bj][m]-1]*=bk;bf[1]*=a3*bl;bf[2]*=a0*bk;bf[5]=+(a3+a0?!!+bf[5]:!+bf[5]);}else{if(S=="H"){for(bi=1,jj=bf[m];bi<jj;bi++){bf[bi]*=bl;}}else{if(S=="V"){for(bi=1,jj=bf[m];bi<jj;bi++){bf[bi]*=bk;}}else{for(bi=1,jj=bf[m];bi<jj;bi++){bf[bi]*=(bi%2)?bl:bk;}}}}}var d=U(bg),ba=bo-d.x-d.width/2,a8=bn-d.y-d.height/2;bg[0][1]+=ba;bg[0][2]+=a8;this.attr({path:bg});break;}if(this.type in {text:1,image:1}&&(a3!=1||a0!=1)){if(this.transformations){this.transformations[2]="scale("[aS](a3,",",a0,")");this.node[v]("transform",this.transformations[az](am));ba=(a3==-1)?-bm.x-(a2||0):bm.x;a8=(a0==-1)?-bm.y-(bd||0):bm.y;this.attr({x:ba,y:a8});bm.fx=a3-1;bm.fy=a0-1;}else{this.node.filterMatrix=" progid:DXImageTransform.Microsoft.Matrix(M11="[aS](a3,", M12=0, M21=0, M22=",a0,", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");be.filter=(this.node.filterMatrix||at)+(this.node.filterOpacity||at);}}else{if(this.transformations){this.transformations[2]=at;this.node[v]("transform",this.transformations[az](am));bm.fx=0;bm.fy=0;}else{this.node.filterMatrix=at;be.filter=(this.node.filterMatrix||at)+(this.node.filterOpacity||at);}}bm.scale=[a6,a5,E,e][az](am);this._.sx=a6;this._.sy=a5;}return this;};ax[aY].clone=function(){var d=this.attr();delete d.scale;delete d.translation;return this.paper[this.type]().attr(d);};var aB=function(d,e){return function(a9,S,a0){a9=H(a9);var a5,a4,E,a1,R="",a8={},a6,a3=0;for(var a2=0,a7=a9.length;a2<a7;a2++){E=a9[a2];if(E[0]=="M"){a5=+E[1];a4=+E[2];}else{a1=n(a5,a4,E[1],E[2],E[3],E[4],E[5],E[6]);if(a3+a1>S){if(e&&!a8.start){a6=an.findDotsAtSegment(a5,a4,E[1],E[2],E[3],E[4],E[5],E[6],(S-a3)/a1);R+=["C",a6.start.x,a6.start.y,a6.m.x,a6.m.y,a6.x,a6.y];if(a0){return R;}a8.start=R;R=["M",a6.x,a6.y+"C",a6.n.x,a6.n.y,a6.end.x,a6.end.y,E[5],E[6]][az]();a3+=a1;a5=+E[5];a4=+E[6];continue;}if(!d&&!e){a6=an.findDotsAtSegment(a5,a4,E[1],E[2],E[3],E[4],E[5],E[6],(S-a3)/a1);return{x:a6.x,y:a6.y,alpha:a6.alpha};}}a3+=a1;a5=+E[5];a4=+E[6];}R+=E;}a8.end=R;a6=d?a3:e?a8:an.findDotsAtSegment(a5,a4,E[1],E[2],E[3],E[4],E[5],E[6],1);a6.alpha&&(a6={x:a6.x,y:a6.y,alpha:a6.alpha});return a6;};},n=aj(function(E,d,a0,S,a6,a5,a4,a3){var R={x:0,y:0},a2=0;for(var a1=0;a1<1.01;a1+=0.01){var e=M(E,d,a0,S,a6,a5,a4,a3,a1);a1&&(a2+=ab.sqrt(aM(R.x-e.x,2)+aM(R.y-e.y,2)));R=e;}return a2;});var ap=aB(1),C=aB(),J=aB(0,1);ax[aY].getTotalLength=function(){if(this.type!="path"){return;}return ap(this.attrs.path);};ax[aY].getPointAtLength=function(d){if(this.type!="path"){return;}return C(this.attrs.path,d);};ax[aY].getSubpath=function(i,e){if(this.type!="path"){return;}if(ab.abs(this.getTotalLength()-e)<0.000001){return J(this.attrs.path,i).end;}var d=J(this.attrs.path,e,1);return i?J(d,i).end:d;};an.easing_formulas={linear:function(d){return d;},"<":function(d){return aM(d,3);},">":function(d){return aM(d-1,3)+1;},"<>":function(d){d=d*2;if(d<1){return aM(d,3)/2;}d-=2;return(aM(d,3)+2)/2;},backIn:function(e){var d=1.70158;return e*e*((d+1)*e-d);},backOut:function(e){e=e-1;var d=1.70158;return e*e*((d+1)*e+d)+1;},elastic:function(i){if(i==0||i==1){return i;}var e=0.3,d=e/4;return aM(2,-10*i)*ab.sin((i-d)*(2*ab.PI)/e)+1;},bounce:function(E){var e=7.5625,i=2.75,d;if(E<(1/i)){d=e*E*E;}else{if(E<(2/i)){E-=(1.5/i);d=e*E*E+0.75;}else{if(E<(2.5/i)){E-=(2.25/i);d=e*E*E+0.9375;}else{E-=(2.625/i);d=e*E*E+0.984375;}}}return d;}};var I={length:0},aR=function(){var a2=+new Date;for(var be in I){if(be!="length"&&I[Q](be)){var bj=I[be];if(bj.stop){delete I[be];I[m]--;continue;}var a0=a2-bj.start,bb=bj.ms,ba=bj.easing,bf=bj.from,a7=bj.diff,E=bj.to,a6=bj.t,a9=bj.prev||0,a1=bj.el,R=bj.callback,a8={},d;if(a0<bb){var S=an.easing_formulas[ba]?an.easing_formulas[ba](a0/bb):a0/bb;for(var bc in bf){if(bf[Q](bc)){switch(Z[bc]){case"along":d=S*bb*a7[bc];E.back&&(d=E.len-d);var bd=C(E[bc],d);a1.translate(a7.sx-a7.x||0,a7.sy-a7.y||0);a7.x=bd.x;a7.y=bd.y;a1.translate(bd.x-a7.sx,bd.y-a7.sy);E.rot&&a1.rotate(a7.r+bd.alpha,bd.x,bd.y);break;case"number":d=+bf[bc]+S*bb*a7[bc];break;case"colour":d="rgb("+[B(O(bf[bc].r+S*bb*a7[bc].r)),B(O(bf[bc].g+S*bb*a7[bc].g)),B(O(bf[bc].b+S*bb*a7[bc].b))][az](",")+")";break;case"path":d=[];for(var bh=0,a5=bf[bc][m];bh<a5;bh++){d[bh]=[bf[bc][bh][0]];for(var bg=1,bi=bf[bc][bh][m];bg<bi;bg++){d[bh][bg]=+bf[bc][bh][bg]+S*bb*a7[bc][bh][bg];}d[bh]=d[bh][az](am);}d=d[az](am);break;case"csv":switch(bc){case"translation":var a4=a7[bc][0]*(a0-a9),a3=a7[bc][1]*(a0-a9);a6.x+=a4;a6.y+=a3;d=a4+am+a3;break;case"rotation":d=+bf[bc][0]+S*bb*a7[bc][0];bf[bc][1]&&(d+=","+bf[bc][1]+","+bf[bc][2]);break;case"scale":d=[+bf[bc][0]+S*bb*a7[bc][0],+bf[bc][1]+S*bb*a7[bc][1],(2 in E[bc]?E[bc][2]:at),(3 in E[bc]?E[bc][3]:at)][az](am);break;case"clip-rect":d=[];var bh=4;while(bh--){d[bh]=+bf[bc][bh]+S*bb*a7[bc][bh];}break;}break;}a8[bc]=d;}}a1.attr(a8);a1._run&&a1._run.call(a1);}else{if(E.along){var bd=C(E.along,E.len*!E.back);a1.translate(a7.sx-(a7.x||0)+bd.x-a7.sx,a7.sy-(a7.y||0)+bd.y-a7.sy);E.rot&&a1.rotate(a7.r+bd.alpha,bd.x,bd.y);}(a6.x||a6.y)&&a1.translate(-a6.x,-a6.y);E.scale&&(E.scale=E.scale+at);a1.attr(E);delete I[be];I[m]--;a1.in_animation=null;an.is(R,"function")&&R.call(a1);}bj.prev=a0;}}an.svg&&a1&&a1.paper.safari();I[m]&&setTimeout(aR);},B=function(d){return d>255?255:(d<0?0:d);},t=function(d,i){if(d==null){return{x:this._.tx,y:this._.ty,toString:u};}this._.tx+=+d;this._.ty+=+i;switch(this.type){case"circle":case"ellipse":this.attr({cx:+d+this.attrs.cx,cy:+i+this.attrs.cy});break;case"rect":case"image":case"text":this.attr({x:+d+this.attrs.x,y:+i+this.attrs.y});break;case"path":var e=ad(this.attrs.path);e[0][1]+=+d;e[0][2]+=+i;this.attr({path:e});break;}return this;};ax[aY].animateWith=function(e,i,d,R,E){I[e.id]&&(i.start=I[e.id].start);return this.animate(i,d,R,E);};ax[aY].animateAlong=ay();ax[aY].animateAlongBack=ay(1);function ay(d){return function(E,i,e,S){var R={back:d};an.is(e,"function")?(S=e):(R.rot=e);E&&E.constructor==ax&&(E=E.attrs.path);E&&(R.along=E);return this.animate(R,i,S);};}ax[aY].onAnimation=function(d){this._run=d||0;return this;};ax[aY].animate=function(be,a5,a4,E){if(an.is(a4,"function")||!a4){E=a4||null;}var a9={},e={},a2={};for(var a6 in be){if(be[Q](a6)){if(Z[Q](a6)){a9[a6]=this.attr(a6);(a9[a6]==null)&&(a9[a6]=j[a6]);e[a6]=be[a6];switch(Z[a6]){case"along":var bc=ap(be[a6]),a7=C(be[a6],bc*!!be.back),R=this.getBBox();a2[a6]=bc/a5;a2.tx=R.x;a2.ty=R.y;a2.sx=a7.x;a2.sy=a7.y;e.rot=be.rot;e.back=be.back;e.len=bc;be.rot&&(a2.r=W(this.rotate())||0);break;case"number":a2[a6]=(e[a6]-a9[a6])/a5;break;case"colour":a9[a6]=an.getRGB(a9[a6]);var a8=an.getRGB(e[a6]);a2[a6]={r:(a8.r-a9[a6].r)/a5,g:(a8.g-a9[a6].g)/a5,b:(a8.b-a9[a6].b)/a5};break;case"path":var S=H(a9[a6],e[a6]);a9[a6]=S[0];var a3=S[1];a2[a6]=[];for(var bb=0,a1=a9[a6][m];bb<a1;bb++){a2[a6][bb]=[0];for(var ba=1,bd=a9[a6][bb][m];ba<bd;ba++){a2[a6][bb][ba]=(a3[bb][ba]-a9[a6][bb][ba])/a5;}}break;case"csv":var d=(be[a6]+at)[z](a),a0=(a9[a6]+at)[z](a);switch(a6){case"translation":a9[a6]=[0,0];a2[a6]=[d[0]/a5,d[1]/a5];break;case"rotation":a9[a6]=(a0[1]==d[1]&&a0[2]==d[2])?a0:[0,d[1],d[2]];a2[a6]=[(d[0]-a9[a6][0])/a5,0,0];break;case"scale":be[a6]=d;a9[a6]=(a9[a6]+at)[z](a);a2[a6]=[(d[0]-a9[a6][0])/a5,(d[1]-a9[a6][1])/a5,0,0];break;case"clip-rect":a9[a6]=(a9[a6]+at)[z](a);a2[a6]=[];var bb=4;while(bb--){a2[a6][bb]=(d[bb]-a9[a6][bb])/a5;}break;}e[a6]=d;}}}}this.stop();this.in_animation=1;I[this.id]={start:be.start||+new Date,ms:a5,easing:a4,from:a9,diff:a2,to:e,el:this,callback:E,t:{x:0,y:0}};++I[m]==1&&aR();return this;};ax[aY].stop=function(){I[this.id]&&I[m]--;delete I[this.id];return this;};ax[aY].translate=function(d,e){return this.attr({translation:d+" "+e});};ax[aY][aA]=function(){return"Rapha\xebl\u2019s object";};an.ae=I;var T=function(d){this.items=[];this[m]=0;if(d){for(var e=0,E=d[m];e<E;e++){if(d[e]&&(d[e].constructor==ax||d[e].constructor==T)){this[this.items[m]]=this.items[this.items[m]]=d[e];this[m]++;}}}};T[aY][f]=function(){var R,d;for(var e=0,E=arguments[m];e<E;e++){R=arguments[e];if(R&&(R.constructor==ax||R.constructor==T)){d=this.items[m];this[d]=this.items[d]=R;this[m]++;}}return this;};T[aY].pop=function(){delete this[this[m]--];return this.items.pop();};for(var y in ax[aY]){if(ax[aY][Q](y)){T[aY][y]=(function(d){return function(){for(var e=0,E=this.items[m];e<E;e++){this.items[e][d][aW](this.items[e],arguments);}return this;};})(y);}}T[aY].attr=function(e,a0){if(e&&an.is(e,"array")&&an.is(e[0],"object")){for(var d=0,S=e[m];d<S;d++){this.items[d].attr(e[d]);}}else{for(var E=0,R=this.items[m];E<R;E++){this.items[E].attr[aW](this.items[E],arguments);}}return this;};T[aY].animate=function(S,e,a2,a1){(an.is(a2,"function")||!a2)&&(a1=a2||null);var d=this.items[m],E=d,a0=this,R;a1&&(R=function(){!--d&&a1.call(a0);});this.items[--E].animate(S,e,a2||R,R);while(E--){this.items[E].animateWith(this.items[d-1],S,e,a2||R,R);}return this;};T[aY].insertAfter=function(e){var d=this.items[m];while(d--){this.items[d].insertAfter(e);}return this;};T[aY].getBBox=function(){var d=[],a0=[],e=[],R=[];for(var E=this.items[m];E--;){var S=this.items[E].getBBox();d[f](S.x);a0[f](S.y);e[f](S.x+S.width);R[f](S.y+S.height);}d=aI[aW](0,d);a0=aI[aW](0,a0);return{x:d,y:a0,width:g[aW](0,e)-d,height:g[aW](0,R)-a0};};an.registerFont=function(e){if(!e.face){return e;}this.fonts=this.fonts||{};var E={w:e.w,face:{},glyphs:{}},i=e.face["font-family"];for(var a0 in e.face){if(e.face[Q](a0)){E.face[a0]=e.face[a0];}}if(this.fonts[i]){this.fonts[i][f](E);}else{this.fonts[i]=[E];}if(!e.svg){E.face["units-per-em"]=G(e.face["units-per-em"],10);for(var R in e.glyphs){if(e.glyphs[Q](R)){var S=e.glyphs[R];E.glyphs[R]={w:S.w,k:{},d:S.d&&"M"+S.d[aP](/[mlcxtrv]/g,function(a1){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a1]||"M";})+"z"};if(S.k){for(var d in S.k){if(S[Q](d)){E.glyphs[R].k[d]=S.k[d];}}}}}}return e;};aT[aY].getFont=function(a2,a3,e,R){R=R||"normal";e=e||"normal";a3=+a3||{normal:400,bold:700,lighter:300,bolder:800}[a3]||400;var S=an.fonts[a2];if(!S){var E=new RegExp("(^|\\s)"+a2[aP](/[^\w\d\s+!~.:_-]/g,at)+"(\\s|$)","i");for(var d in an.fonts){if(an.fonts[Q](d)){if(E.test(d)){S=an.fonts[d];break;}}}}var a0;if(S){for(var a1=0,a4=S[m];a1<a4;a1++){a0=S[a1];if(a0.face["font-weight"]==a3&&(a0.face["font-style"]==e||!a0.face["font-style"])&&a0.face["font-stretch"]==R){break;}}}return a0;};aT[aY].print=function(R,E,d,a1,a2,bb){bb=bb||"middle";var a7=this.set(),ba=(d+at)[z](at),a8=0,a4=at,bc;an.is(a1,"string")&&(a1=this.getFont(a1));if(a1){bc=(a2||16)/a1.face["units-per-em"];var e=a1.face.bbox.split(a),a0=+e[0],a3=+e[1]+(bb=="baseline"?e[3]-e[1]+(+a1.face.descent):(e[3]-e[1])/2);for(var a6=0,S=ba[m];a6<S;a6++){var a5=a6&&a1.glyphs[ba[a6-1]]||{},a9=a1.glyphs[ba[a6]];a8+=a6?(a5.w||a1.w)+(a5.k&&a5.k[ba[a6]]||0):0;a9&&a9.d&&a7[f](this.path(a9.d).attr({fill:"#000",stroke:"none",translation:[a8,0]}));}a7.scale(bc,bc,a0,a3).translate(R-a0,E-a3);}return a7;};an.format=function(i){var e=an.is(arguments[1],"array")?[0][aS](arguments[1]):arguments,d=/\{(\d+)\}/g;i&&an.is(i,"string")&&e[m]-1&&(i=i[aP](d,function(R,E){return e[++E]==null?at:e[E];}));return i||at;};an.ninja=function(){var d=Raphael;if(l.was){Raphael=l.is;}else{delete Raphael;}return d;};an.el=ax[aY];return an;})();
\ No newline at end of file
--- /dev/null
+// seedrandom.js
+// Author: David Bau 3/11/2010
+//
+// Defines a method Math.seedrandom() that, when called, substitutes
+// an explicitly seeded RC4-based algorithm for Math.random(). Also
+// supports automatic seeding from local or network sources of entropy.
+//
+// Usage:
+//
+// <script src=http://davidbau.com/encode/seedrandom-min.js></script>
+//
+// Math.seedrandom('yipee'); Sets Math.random to a function that is
+// initialized using the given explicit seed.
+//
+// Math.seedrandom(); Sets Math.random to a function that is
+// seeded using the current time, dom state,
+// and other accumulated local entropy.
+// The generated seed string is returned.
+//
+// Math.seedrandom('yowza', true);
+// Seeds using the given explicit seed mixed
+// together with accumulated entropy.
+//
+// <script src="http://bit.ly/srandom-512"></script>
+// Seeds using physical random bits downloaded
+// from random.org.
+//
+// Examples:
+//
+// Math.seedrandom("hello"); // Use "hello" as the seed.
+// document.write(Math.random()); // Always 0.5463663768140734
+// document.write(Math.random()); // Always 0.43973793770592234
+// var rng1 = Math.random; // Remember the current prng.
+//
+// var autoseed = Math.seedrandom(); // New prng with an automatic seed.
+// document.write(Math.random()); // Pretty much unpredictable.
+//
+// Math.random = rng1; // Continue "hello" prng sequence.
+// document.write(Math.random()); // Always 0.554769432473455
+//
+// Math.seedrandom(autoseed); // Restart at the previous seed.
+// document.write(Math.random()); // Repeat the 'unpredictable' value.
+//
+// Notes:
+//
+// Each time seedrandom('arg') is called, entropy from the passed seed
+// is accumulated in a pool to help generate future seeds for the
+// zero-argument form of Math.seedrandom, so entropy can be injected over
+// time by calling seedrandom with explicit data repeatedly.
+//
+// On speed - This javascript implementation of Math.random() is about
+// 3-10x slower than the built-in Math.random() because it is not native
+// code, but this is typically fast enough anyway. Seeding is more expensive,
+// especially if you use auto-seeding. Some details (timings on Chrome 4):
+//
+// Our Math.random() - avg less than 0.002 milliseconds per call
+// seedrandom('explicit') - avg less than 0.5 milliseconds per call
+// seedrandom('explicit', true) - avg less than 2 milliseconds per call
+// seedrandom() - avg about 38 milliseconds per call
+//
+// LICENSE (BSD):
+//
+// Copyright 2010 David Bau, all rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// 3. Neither the name of this module nor the names of its contributors may
+// be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+/**
+ * All code is in an anonymous closure to keep the global namespace clean.
+ *
+ * @param {number=} overflow
+ * @param {number=} startdenom
+ */
+(function (pool, math, width, chunks, significance, overflow, startdenom) {
+
+
+//
+// seedrandom()
+// This is the seedrandom function described above.
+//
+math['seedrandom'] = function seedrandom(seed, use_entropy) {
+ var key = [];
+ var arc4;
+
+ // Flatten the seed string or build one from local entropy if needed.
+ seed = mixkey(flatten(
+ use_entropy ? [seed, pool] :
+ arguments.length ? seed :
+ [new Date().getTime(), pool, window], 3), key);
+
+ // Use the seed to initialize an ARC4 generator.
+ arc4 = new ARC4(key);
+
+ // Mix the randomness into accumulated entropy.
+ mixkey(arc4.S, pool);
+
+ // Override Math.random
+
+ // This function returns a random double in [0, 1) that contains
+ // randomness in every bit of the mantissa of the IEEE 754 value.
+
+ math['random'] = function random() { // Closure to return a random double:
+ var n = arc4.g(chunks); // Start with a numerator n < 2 ^ 48
+ var d = startdenom; // and denominator d = 2 ^ 48.
+ var x = 0; // and no 'extra last byte'.
+ while (n < significance) { // Fill up all significant digits by
+ n = (n + x) * width; // shifting numerator and
+ d *= width; // denominator and generating a
+ x = arc4.g(1); // new least-significant-byte.
+ }
+ while (n >= overflow) { // To avoid rounding up, before adding
+ n /= 2; // last byte, shift everything
+ d /= 2; // right using integer math until
+ x >>>= 1; // we have exactly the desired bits.
+ }
+ return (n + x) / d; // Form the number within [0, 1).
+ };
+
+ // Return the seed that was used
+ return seed;
+};
+
+//
+// ARC4
+//
+// An ARC4 implementation. The constructor takes a key in the form of
+// an array of at most (width) integers that should be 0 <= x < (width).
+//
+// The g(count) method returns a pseudorandom integer that concatenates
+// the next (count) outputs from ARC4. Its return value is a number x
+// that is in the range 0 <= x < (width ^ count).
+//
+/** @constructor */
+function ARC4(key) {
+ var t, u, me = this, keylen = key.length;
+ var i = 0, j = me.i = me.j = me.m = 0;
+ me.S = [];
+ me.c = [];
+
+ // The empty key [] is treated as [0].
+ if (!keylen) { key = [keylen++]; }
+
+ // Set up S using the standard key scheduling algorithm.
+ while (i < width) { me.S[i] = i++; }
+ for (i = 0; i < width; i++) {
+ t = me.S[i];
+ j = lowbits(j + t + key[i % keylen]);
+ u = me.S[j];
+ me.S[i] = u;
+ me.S[j] = t;
+ }
+
+ // The "g" method returns the next (count) outputs as one number.
+ me.g = function getnext(count) {
+ var s = me.S;
+ var i = lowbits(me.i + 1); var t = s[i];
+ var j = lowbits(me.j + t); var u = s[j];
+ s[i] = u;
+ s[j] = t;
+ var r = s[lowbits(t + u)];
+ while (--count) {
+ i = lowbits(i + 1); t = s[i];
+ j = lowbits(j + t); u = s[j];
+ s[i] = u;
+ s[j] = t;
+ r = r * width + s[lowbits(t + u)];
+ }
+ me.i = i;
+ me.j = j;
+ return r;
+ };
+ // For robust unpredictability discard an initial batch of values.
+ // See http://www.rsa.com/rsalabs/node.asp?id=2009
+ me.g(width);
+}
+
+//
+// flatten()
+// Converts an object tree to nested arrays of strings.
+//
+/** @param {Object=} result
+ * @param {string=} prop */
+function flatten(obj, depth, result, prop) {
+ result = [];
+ if (depth && typeof(obj) == 'object') {
+ for (prop in obj) {
+ if (prop.indexOf('S') < 5) { // Avoid FF3 bug (local/sessionStorage)
+ try { result.push(flatten(obj[prop], depth - 1)); } catch (e) {}
+ }
+ }
+ }
+ return result.length ? result : '' + obj;
+}
+
+//
+// mixkey()
+// Mixes a string seed into a key that is an array of integers, and
+// returns a shortened string seed that is equivalent to the result key.
+//
+/** @param {number=} smear
+ * @param {number=} j */
+function mixkey(seed, key, smear, j) {
+ seed += ''; // Ensure the seed is a string
+ smear = 0;
+ for (j = 0; j < seed.length; j++) {
+ key[lowbits(j)] =
+ lowbits((smear ^= key[lowbits(j)] * 19) + seed.charCodeAt(j));
+ }
+ seed = '';
+ for (j in key) { seed += String.fromCharCode(key[j]); }
+ return seed;
+}
+
+//
+// lowbits()
+// A quick "n mod width" for width a power of 2.
+//
+function lowbits(n) { return n & (width - 1); }
+
+//
+// The following constants are related to IEEE 754 limits.
+//
+startdenom = math.pow(width, chunks);
+significance = math.pow(2, significance);
+overflow = significance * 2;
+
+//
+// When seedrandom.js is loaded, we immediately mix a few bits
+// from the built-in RNG into the entropy pool. Because we do
+// not want to intefere with determinstic PRNG state later,
+// seedrandom will not call math.random on its own again after
+// initialization.
+//
+mixkey(math.random(), pool);
+
+// End anonymous scope, and pass initial values.
+})(
+ [], // pool: entropy pool starts empty
+ Math, // math: package containing random, pow, and seedrandom
+ 256, // width: each RC4 output is 0 <= x < 256
+ 6, // chunks: at least six RC4 outputs for each double
+ 52 // significance: there are 52 significant digits in a double
+);
--- /dev/null
+ table {
+ width:90%;
+ border-top:1px solid #e5eaf8;
+ border-right:1px solid #e5eaf8;
+ margin:1em auto;
+ border-collapse:collapse;
+ }
+
+ td {
+ color:#678197;
+ border-bottom:1px solid #e6eff8;
+ border-left:1px solid #e6eff8;
+ padding:.3em 1em;
+ text-align:center;
+ }
+ th {
+ background:#f4f9fe;
+ text-align:center;
+ font:bold 1.2em/2em Century Gothic,Trebuchet MS,Arial,Helvetica,sans-serif;
+ color:#66a3d3;
+ }
+
--- /dev/null
+# Copyright (C) 2011 Fundacio Privada per a la Xarxa Oberta, Lliure i Neutral guifi.net
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The full GNU General Public License is included in this distribution in
+# the file called "COPYING".
+#
+# Contibutors:
+# Axel Neumann, Simó Albert i Beltran, Pau Escrich
+#
+
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=bmx6-qmp
+
+PKG_SOURCE_PROTO:=git
+
+Public Sources:
+#PKG_SOURCE_URL:=git://git.bmx6.net/bmx6.git
+PKG_SOURCE_URL:=git://github.com/axn/bmx6.git
+
+PKG_REV:=7219010098ea67f8ea08a06a68e7a765b114ca16
+
+Private Sources:
+#PKG_SOURCE_URL:=file:///usr/src/bmx6/bmx6-private.git
+
+PKG_VERSION:=r2013022001
+
+PKG_RELEASE:=4
+#PKG_INSTALL:=1 # this tries to install straight to /usr/sbin/bmx6
+
+PKG_SOURCE_VERSION:=$(PKG_REV)
+PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
+PKG_SOURCE:=$(PKG_SOURCE_SUBDIR).tar.gz
+PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_SOURCE_SUBDIR)
+
+include $(INCLUDE_DIR)/package.mk
+
+
+TARGET_CFLAGS += $(FPIC)
+
+#-DNO_TRAFFIC_DUMP -DNO_DYN_PLUGIN -DNO_DEBUG_DUMP -DNO_DEBUG_ALL -DNO_DEBUG_TRACK -DNO_DEBUG_SYS
+
+MAKE_ARGS += \
+ EXTRA_CFLAGS="$(TARGET_CFLAGS) -I. -I$(STAGING_DIR)/usr/include -DNO_DEBUG_ALL -DNO_DEBUG_DUMP" \
+ EXTRA_LDFLAGS="-L$(STAGING_DIR)/usr/lib " \
+ REVISION_VERSION="$(PKG_REV)" \
+ CC="$(TARGET_CC)" \
+ INSTALL_DIR="$(PKG_INSTALL_DIR)" \
+ STRIP="/bin/false" \
+ build_all
+
+
+define Package/bmx6-qmp/Default
+ SECTION:=net
+ CATEGORY:=qMp
+ TITLE:=BMX6 layer 3 routing daemon (QMP version)
+ URL:=http://bmx6.net/
+ MAINTAINER:=Axel Neumann <neumann@cgws.de>
+endef
+
+define Package/bmx6-qmp/description
+BMX6 layer 3 routing daemon (QMP version) supporting IPv4, IPv6, and IPv4 over IPv6 - http://www.bmx6.net
+endef
+
+define Package/bmx6-qmp
+ $(call Package/bmx6-qmp/Default)
+ MENU:=1
+endef
+
+define Package/bmx6-qmp-uci-config
+ $(call Package/bmx6-qmp/Default)
+ DEPENDS:=bmx6-qmp +libuci
+ TITLE:=configuration plugin based on uci (recommended!)
+endef
+
+
+define Package/bmx6-qmp-json
+ $(call Package/bmx6-qmp/Default)
+ DEPENDS:=bmx6-qmp +libjson
+ TITLE:=josn plugin based on jsonc
+endef
+
+define Package/bmx6-qmp-sms
+ $(call Package/bmx6-qmp/Default)
+ DEPENDS:=bmx6-qmp
+ TITLE:=sms plugin
+endef
+
+define Package/bmx6-qmp-quagga
+ $(call Package/bmx6-qmp/Default)
+ DEPENDS:=bmx6-qmp +qmp-quagga
+ TITLE:=bmx6 quagga plugin to redistribute/export routes (needs manet/bmx6 patched quagga 0.99.21)
+endef
+
+define Build/Configure
+ mkdir -p $(PKG_INSTALL_DIR)
+endef
+
+define Build/Compile
+ $(MAKE) -C $(PKG_BUILD_DIR) $(MAKE_ARGS)
+endef
+
+
+define Package/bmx6-qmp/install
+ $(INSTALL_DIR) $(1)/usr/sbin $(1)/etc/config $(1)/etc/init.d
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/bmx6 $(1)/usr/sbin/bmx6
+endef
+
+
+define Package/bmx6-qmp-uci-config/conffiles
+/etc/config/bmx6
+endef
+
+
+define Package/bmx6-qmp-uci-config/install
+ $(INSTALL_DIR) $(1)/usr/lib $(1)/etc/config $(1)/etc/init.d
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/lib/bmx6_uci_config/bmx6_config.so $(1)/usr/lib/bmx6_config.so
+ $(INSTALL_BIN) ./files/etc/init.d/bmx6 $(1)/etc/init.d/bmx6
+ $(INSTALL_DATA) ./files/etc/config/bmx6 $(1)/etc/config/bmx6
+endef
+
+define Package/bmx6-qmp-json/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/lib/bmx6_json/bmx6_json.so $(1)/usr/lib/bmx6_json.so
+endef
+
+define Package/bmx6-qmp-sms/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/lib/bmx6_sms/bmx6_sms.so $(1)/usr/lib/bmx6_sms.so
+endef
+
+define Package/bmx6-qmp-quagga/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(INSTALL_BIN) $(PKG_BUILD_DIR)/lib/bmx6_quagga/bmx6_quagga.so $(1)/usr/lib/bmx6_quagga.so
+endef
+
+$(eval $(call BuildPackage,bmx6-qmp))
+$(eval $(call BuildPackage,bmx6-qmp-uci-config))
+$(eval $(call BuildPackage,bmx6-qmp-json))
+$(eval $(call BuildPackage,bmx6-qmp-sms))
+$(eval $(call BuildPackage,bmx6-qmp-quagga))
+
+
--- /dev/null
+
+# for more information:
+# http://bmx6.net/projects/bmx6/wiki
+# options execute: bmx6 --help
+
+config 'bmx6' 'general'
+# option 'runtimeDir' '/var/run/bmx6'
+# option 'tun4Address' '10.202.0.116/32'
+# option 'tun4Address' '10.254.10.0/32'
+# option 'tun6Address' '2012:0:0:1000::1/64'
+
+#config 'ipVersion' 'ipVersion'
+# option 'ipVersion' '6' # default is 4
+# option 'throwRules' '0'
+
+
+#config 'plugin'
+# option 'plugin' 'bmx6_config.so'
+
+
+
+#config 'plugin'
+# option 'plugin' 'bmx6_json.so'
+
+
+
+#config 'plugin'
+# option 'plugin' 'bmx6_sms.so'
+
+
+config 'dev' 'mesh_1'
+ option 'dev' 'eth0.12'
+
+config 'dev' 'mesh_2'
+ option 'dev' 'ath0.12'
+
+
+
+#config 'hna' 'my_global_prefix'
+# option 'hna' '2012:0:0:74:0:0:0:0/64'
+
+
+#config 'tunOut'
+# option 'tunOut' 'ip6'
+# option 'network' '2012::/16'
+# option 'exportDistance' '0'
+
+#config 'tunOut'
+# option 'tunOut' 'ip4'
+# option 'network' '10.254.0.0/16'
+# option 'exportDistance' '0' # requires quagga plugin !
+# option 'minPrefixLen' '27'
+
+
+
+#config 'plugin'
+# option 'plugin' 'bmx6_quagga.so'
+
+
+
+#config 'redistribute'
+# option 'redistribute' 'ospf6'
+# option 'network' '10.0.0.0/8'
+# option 'minPrefixLen' '10'
+# option 'bandwidth' '10000000'
+# option 'ospf6' '1'
+# option 'aggregatePrefixLen' '16'
+
+#config 'redistribute'
+# option 'redistribute' 'bgp'
+# option 'network' '0.0.0.0/0'
+# option 'minPrefixLen' '0'
+# option 'maxPrefixLen' '24'
+# option 'bandwidth' '10000000'
+# option 'bgp' '1'
+# option 'aggregatePrefixLen' '8'
+
+
+
+
+
+
--- /dev/null
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2011 Fundacio Privada per a la Xarxa Oberta, Lliure i Neutral guifi.net
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# The full GNU General Public License is included in this distribution in
+# the file called "COPYING".
+
+START=91
+
+BIN=/usr/sbin/bmx6
+CONF=/etc/config/bmx6
+PID=/var/run/bmx6/pid
+
+
+start() {
+ cd /root/
+ ulimit -c 20000
+ $BIN -f $CONF -d0 > /dev/null &
+# start-stop-daemon -b -x $BIN -S -- -f $CONF
+}
+
+stop() {
+ start-stop-daemon -p $PID -K
+}
+
+restart() {
+ stop; sleep 3; start
+}
--- /dev/null
+#
+# Copyright (C) 2006-2012 OpenWrt.org
+#
+# This is free software, licensed under the GNU General Public License v2.
+# See /LICENSE for more information.
+#
+# Contributors:
+# Simó Albert i Beltran
+#
+
+include $(TOPDIR)/rules.mk
+
+PKG_NAME:=qmp-quagga
+PKG_VERSION:=0.99.21
+PKG_RELEASE:=2
+PKG_MD5SUM:=99840adbe57047c90dfba6b6ed9aec7f
+
+PKG_SOURCE:=quagga-$(PKG_VERSION).tar.gz
+PKG_SOURCE_URL:=http://www.quagga.net/download/ \
+ http://www.de.quagga.net/download/ \
+ http://www.uk.quagga.net/download/
+
+PKG_BUILD_DIR:=$(BUILD_DIR)/quagga-$(PKG_VERSION)
+
+PKG_CONFIG_DEPENDS:= \
+ CONFIG_IPV6 \
+ CONFIG_PACKAGE_qmp-quagga-watchquagga \
+ CONFIG_PACKAGE_qmp-quagga-zebra \
+ CONFIG_PACKAGE_qmp-quagga-libzebra \
+ CONFIG_PACKAGE_qmp-quagga-libospf \
+ CONFIG_PACKAGE_qmp-quagga-bgpd \
+ CONFIG_PACKAGE_qmp-quagga-isisd \
+ CONFIG_PACKAGE_qmp-quagga-ospf6d \
+ CONFIG_PACKAGE_qmp-quagga-ripd \
+ CONFIG_PACKAGE_qmp-quagga-ripngd \
+ CONFIG_PACKAGE_qmp-quagga-babeld \
+ CONFIG_PACKAGE_qmp-quagga-vtysh
+PKG_BUILD_PARALLEL:=1
+PKG_FIXUP:=autoreconf
+PKG_INSTALL:=1
+
+include $(INCLUDE_DIR)/package.mk
+
+define Package/qmp-quagga/Default
+ SECTION:=qMp
+ CATEGORY:=qMp
+ SUBMENU:=Routing and Redirection
+ DEPENDS:=qmp-quagga
+ TITLE:=The Quagga Software Routing Suite
+ URL:=http://www.quagga.net
+ MAINTAINER:=Vasilis Tsiligiannis <b_tsiligiannis@silverton.gr>
+endef
+
+define Package/qmp-quagga
+ $(call Package/qmp-quagga/Default)
+ DEPENDS:=+qmp-quagga-vtysh +qmp-quagga-bgpd
+ MENU:=1
+endef
+
+define Package/qmp-quagga/description
+ A routing software package that provides TCP/IP based routing services
+ with routing protocols support such as RIPv1, RIPv2, RIPng, OSPFv2,
+ OSPFv3, BGP-4, and BGP-4+
+endef
+
+define Package/qmp-quagga-watchquagga
+ $(call Package/qmp-quagga/Default)
+ TITLE:=Quagga watchdog
+ DEPENDS+=+qmp-quagga-libzebra
+ DEFAULT:=y if PACKAGE_qmp-quagga
+endef
+
+define Package/qmp-quagga-zebra
+ $(call Package/qmp-quagga/Default)
+ TITLE:=Zebra daemon
+ DEPENDS+=+qmp-quagga-libzebra
+ DEFAULT:=y if PACKAGE_qmp-quagga
+endef
+
+define Package/qmp-quagga-libzebra
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+librt
+ TITLE:=zebra library
+endef
+
+define Package/qmp-quagga-libospf
+ $(call Package/qmp-quagga/Default)
+ TITLE:=OSPF library
+endef
+
+define Package/qmp-quagga-bgpd
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libzebra
+ TITLE:=BGPv4, BGPv4+, BGPv4- routing engine
+endef
+
+define Package/qmp-quagga-isisd
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libzebra
+ TITLE:=IS-IS routing engine
+endef
+
+define Package/qmp-quagga-ospfd
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libospf +qmp-quagga-libzebra
+ TITLE:=OSPFv2 routing engine
+endef
+
+define Package/qmp-quagga-ospf6d
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libospf +qmp-quagga-libzebra @IPV6
+ TITLE:=OSPFv3 routing engine
+endef
+
+define Package/qmp-quagga-ripd
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libzebra
+ TITLE:=RIP routing engine
+endef
+
+define Package/qmp-quagga-ripngd
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libzebra @IPV6
+ TITLE:=RIPNG routing engine
+endef
+
+define Package/qmp-quagga-babeld
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libzebra
+ TITLE:=Babel routing engine
+endef
+
+define Package/qmp-quagga-vtysh
+ $(call Package/qmp-quagga/Default)
+ DEPENDS+=+qmp-quagga-libzebra +libreadline +libncurses
+ TITLE:=integrated shell for Quagga routing software
+endef
+
+define Package/qmp-quagga-zebra/conffiles
+/etc/quagga/zebra.conf
+endef
+
+define Package/qmp-quagga-bgpd/conffiles
+/etc/quagga/bgpd.conf
+endef
+
+define Package/qmp-quagga-isisd/conffiles
+/etc/quagga/isisd.conf
+endef
+
+define Package/qmp-quagga-ospfd/conffiles
+/etc/quagga/ospfd.conf
+endef
+
+define Package/qmp-quagga-ospf6d/conffiles
+/etc/quagga/ospf6d.conf
+endef
+
+define Package/qmp-quagga-ripd/conffiles
+/etc/quagga/ripd.conf
+endef
+
+define Package/qmp-quagga-ripngd/conffiles
+/etc/quagga/ripngd.conf
+endef
+
+define Package/qmp-quagga-babeld/conffiles
+/etc/quagga/babeld.conf
+endef
+
+ifneq ($(SDK),)
+CONFIG_PACKAGE_qmp-quagga-libzebra:=m
+CONFIG_PACKAGE_qmp-quagga-libospf:=m
+CONFIG_PACKAGE_qmp-quagga-watchquagga:=m
+CONFIG_PACKAGE_qmp-quagga-zebra:=m
+CONFIG_PACKAGE_qmp-quagga-bgpd:=m
+CONFIG_PACKAGE_qmp-quagga-isisd:=m
+CONFIG_PACKAGE_qmp-quagga-ospf6d:=m
+CONFIG_PACKAGE_qmp-quagga-ripd:=m
+CONFIG_PACKAGE_qmp-quagga-ripngd:=m
+CONFIG_PACKAGE_qmp-quagga-babeld:=m
+CONFIG_PACKAGE_qmp-quagga-vtysh:=m
+endif
+
+CONFIGURE_ARGS+= \
+ --localstatedir=/var/run/quagga \
+ --sysconfdir=/etc/quagga/ \
+ --enable-shared \
+ --disable-static \
+ --enable-user=network \
+ --enable-group=network \
+ --enable-pie=no \
+ --enable-multipath=8 \
+ --disable-ospfclient \
+ --disable-capabilities \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-libzebra,zebra) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-libospf,ospfd) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-bgpd,bgpd) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-isisd,isisd) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-ospf6d,ospf6d) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-ripd,ripd) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-ripngd,ripngd) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-babeld,babeld) \
+ $(call autoconf_bool,CONFIG_PACKAGE_qmp-quagga-vtysh,vtysh) \
+
+MAKE_FLAGS += \
+ CFLAGS="$(TARGET_CFLAGS) -std=gnu99"
+
+define Package/qmp-quagga/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) ./files/quagga $(1)/usr/sbin/quagga.init
+ $(INSTALL_DIR) $(1)/etc/init.d
+ $(INSTALL_BIN) ./files/quagga.init $(1)/etc/init.d/quagga
+endef
+
+define Package/qmp-quagga-watchquagga/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/watchquagga $(1)/usr/sbin/
+endef
+
+define Package/qmp-quagga-zebra/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/zebra $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/zebra.conf
+endef
+
+define Package/qmp-quagga-bgpd/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/bgpd $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/bgpd.conf
+endef
+
+define Package/qmp-quagga-isisd/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/isisd $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/isisd.conf
+endef
+
+define Package/qmp-quagga-ospfd/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/ospfd $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/ospfd.conf
+endef
+
+define Package/qmp-quagga-ospf6d/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/ospf6d $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/ospf6d.conf
+endef
+
+define Package/qmp-quagga-ripd/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/ripd $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/ripd.conf
+endef
+
+define Package/qmp-quagga-ripngd/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/ripngd $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/ripngd.conf
+endef
+
+define Package/qmp-quagga-babeld/install
+ $(INSTALL_DIR) $(1)/usr/sbin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/sbin/babeld $(1)/usr/sbin/
+ $(INSTALL_DIR) $(1)/etc/quagga
+ chmod 0750 $(1)/etc/quagga
+ $(INSTALL_CONF) ./files/quagga.conf $(1)/etc/quagga/babeld.conf
+endef
+
+define Package/qmp-quagga-vtysh/install
+ $(INSTALL_DIR) $(1)/usr/bin
+ $(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/vtysh $(1)/usr/bin/
+endef
+
+define Package/qmp-quagga-libospf/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(CP) $(PKG_INSTALL_DIR)/usr/lib/libospf.so.* $(1)/usr/lib/
+endef
+
+define Package/qmp-quagga-libzebra/install
+ $(INSTALL_DIR) $(1)/usr/lib
+ $(CP) $(PKG_INSTALL_DIR)/usr/lib/libzebra.so.* $(1)/usr/lib/
+endef
+
+$(eval $(call BuildPackage,qmp-quagga))
+$(eval $(call BuildPackage,qmp-quagga-libzebra))
+$(eval $(call BuildPackage,qmp-quagga-libospf))
+$(eval $(call BuildPackage,qmp-quagga-watchquagga))
+$(eval $(call BuildPackage,qmp-quagga-zebra))
+$(eval $(call BuildPackage,qmp-quagga-bgpd))
+$(eval $(call BuildPackage,qmp-quagga-isisd))
+$(eval $(call BuildPackage,qmp-quagga-ospfd))
+$(eval $(call BuildPackage,qmp-quagga-ospf6d))
+$(eval $(call BuildPackage,qmp-quagga-ripd))
+$(eval $(call BuildPackage,qmp-quagga-ripngd))
+$(eval $(call BuildPackage,qmp-quagga-babeld))
+$(eval $(call BuildPackage,qmp-quagga-vtysh))
--- /dev/null
+#!/bin/sh
+#
+# quagga Starts/stops quagga daemons and watchquagga.
+# Create a daemon.conf file to have that routing daemon
+# started/stopped automagically when using this script
+# without any daemon names as args.
+# If watchquagga is available, it will also be
+# started/stopped if the script is called without
+# any daemon names.
+#
+
+ME=$(basename $0)
+
+usage() {
+ echo "Usage: ${ME} {start|stop|restart} [daemon ...]"
+ exit 2
+}
+
+if [ -z "$1" ]
+then
+ usage
+else
+ COMMAND=$1
+fi
+shift
+ARG_DAEMONS=$*
+BINDIR=/usr/sbin
+CONFDIR=/etc/quagga
+STATEDIR=/var/run/quagga
+RUNUSER=network
+RUNGROUP=$RUNUSER
+DAEMONS="zebra ripd ripngd ospfd ospf6d bgpd"
+DAEMON_FLAGS=-d
+WATCHQUAGGA_FLAGS="-d -z -T 60 -R"
+WATCHQUAGGA_CMD="$0 watchrestart"
+if [ ${COMMAND} != "watchrestart" ]
+then
+ DAEMONS="${DAEMONS} watchquagga"
+fi
+DAEMONS_STARTSEQ=${DAEMONS}
+
+reverse()
+{
+ local revlist r
+ revlist=
+ for r
+ do
+ revlist="$r $revlist"
+ done
+ echo $revlist
+}
+
+DAEMONS_STOPSEQ=$(reverse ${DAEMONS_STARTSEQ})
+
+#pidof() {
+# ps ax | awk 'match($5, "(^|/)'"$1"'$") > 0 { printf " %s", $1 }'
+#}
+
+quit() {
+ echo "${ME}: $1"
+ exit 0
+}
+
+die() {
+ echo "${ME}: $1"
+ exit 1
+}
+
+is_in() {
+ local i
+ for i in $2
+ do
+ [ "$1" = "$i" ] && return 0
+ done
+ return 1
+}
+
+select_subset() {
+ local unknown i j
+ unknown=
+ RESULT=
+ for i in $1
+ do
+ is_in $i "$2" || unknown="$unknown $i"
+ done
+ if [ -n "$unknown" ]
+ then
+ RESULT=$unknown
+ return 1
+ else
+ for j in $2
+ do
+ is_in $j "$1" && RESULT="$RESULT $j"
+ done
+ return 0
+ fi
+}
+
+# check command
+
+case ${COMMAND}
+in
+ start|stop|restart)
+ ;;
+ watchrestart)
+ if [ -n "$ARG_DAEMONS" ]
+ then
+ echo "${ME}: watchrestart mode is only for use by watchquagga"
+ exit 2
+ fi
+ ;;
+ *)
+ usage
+ ;;
+esac
+
+# select daemons to start
+
+case ${COMMAND}
+in
+ start|restart|watchrestart)
+ START_DAEMONS=
+ for d in ${DAEMONS_STARTSEQ}
+ do
+ [ -x "${BINDIR}/${d}" -a -f "${CONFDIR}/${d}.conf" ] \
+ && START_DAEMONS="${START_DAEMONS}${d} "
+ done
+ WATCHQUAGGA_DAEMONS=${START_DAEMONS}
+ if is_in watchquagga "${DAEMONS_STARTSEQ}"
+ then
+ START_DAEMONS="${START_DAEMONS} watchquagga"
+ fi
+ if [ -n "${ARG_DAEMONS}" ]
+ then
+ if select_subset "${ARG_DAEMONS}" "${DAEMONS}"
+ then
+ if select_subset "${ARG_DAEMONS}" "${START_DAEMONS}"
+ then
+ START_DAEMONS=${RESULT}
+ else
+ die "these daemons are not startable:${RESULT}."
+ fi
+ else
+ die "unknown daemons:${RESULT}; choose from: ${DAEMONS}."
+ fi
+ fi
+ ;;
+esac
+
+# select daemons to stop
+
+case ${COMMAND}
+in
+ stop|restart|watchrestart)
+ STOP_DAEMONS=${DAEMONS_STOPSEQ}
+ if [ -n "${ARG_DAEMONS}" ]
+ then
+ if select_subset "${ARG_DAEMONS}" "${STOP_DAEMONS}"
+ then
+ STOP_DAEMONS=${RESULT}
+ else
+ die "unknown daemons:${RESULT}; choose from: ${DAEMONS}."
+ fi
+ fi
+ stop_daemons=
+ for d in ${STOP_DAEMONS}
+ do
+ pidfile=${STATEDIR}/${d}.pid
+ if [ -f "${pidfile}" -o -n "$(pidof ${d})" ]
+ then
+ stop_daemons="${stop_daemons}${d} "
+ elif [ -n "${ARG_DAEMONS}" ]
+ then
+ echo "${ME}: found no ${d} process running."
+ fi
+ done
+ STOP_DAEMONS=${stop_daemons}
+ ;;
+esac
+
+# stop daemons
+
+for d in $STOP_DAEMONS
+do
+ echo -n "${ME}: Stopping ${d} ... "
+ pidfile=${STATEDIR}/${d}.pid
+ if [ -f "${pidfile}" ]
+ then
+ file_pid=$(cat ${pidfile})
+ if [ -z "${file_pid}" ]
+ then
+ echo -n "no pid file entry found ... "
+ fi
+ else
+ file_pid=
+ echo -n "no pid file found ... "
+ fi
+ proc_pid=$(pidof ${d})
+ if [ -z "${proc_pid}" ]
+ then
+ echo -n "found no ${d} process running ... "
+ else
+ count=0
+ notinpidfile=
+ for p in ${proc_pid}
+ do
+ count=$((${count}+1))
+ if kill ${p}
+ then
+ echo -n "killed ${p} ... "
+ else
+ echo -n "failed to kill ${p} ... "
+ fi
+ [ "${p}" = "${file_pid}" ] \
+ || notinpidfile="${notinpidfile} ${p}"
+ done
+ [ ${count} -le 1 ] \
+ || echo -n "WARNING: ${count} ${d} processes were found running ... "
+ for n in ${notinpidfile}
+ do
+ echo -n "WARNING: process ${n} was not in pid file ... "
+ done
+ fi
+ count=0
+ survivors=$(pidof ${d})
+ while [ -n "${survivors}" ]
+ do
+ sleep 1
+ count=$((${count}+1))
+ survivors=$(pidof ${d})
+ [ -z "${survivors}" -o ${count} -gt 5 ] && break
+ for p in ${survivors}
+ do
+ sleep 1
+ echo -n "${p} "
+ kill ${p}
+ done
+ done
+ survivors=$(pidof ${d})
+ [ -n "${survivors}" ] && \
+ if kill -KILL ${survivors}
+ then
+ echo -n "KILLed ${survivors} ... "
+ else
+ echo -n "failed to KILL ${survivors} ... "
+ fi
+ sleep 1
+ survivors=$(pidof ${d})
+ if [ -z "${survivors}" ]
+ then
+ echo -n "done."
+ if [ -f "${pidfile}" ]
+ then
+ rm -f ${pidfile} \
+ || echo -n " Failed to remove pidfile."
+ fi
+ else
+ echo -n "failed to stop ${survivors} - giving up."
+ if [ "${survivors}" != "${file_pid}" ]
+ then
+ if echo "${survivors}" > ${pidfile}
+ then
+ chown ${RUNUSER}:${RUNGROUP} ${pidfile}
+ echo -n " Wrote ${survivors} to pidfile."
+ else
+ echo -n " Failed to write ${survivors} to pidfile."
+ fi
+ fi
+ fi
+ echo
+done
+
+# start daemons
+
+if [ -n "$START_DAEMONS" ]
+then
+ [ -d ${CONFDIR} ] \
+ || quit "${ME}: no config directory ${CONFDIR} - exiting."
+ chown -R ${RUNUSER}:${RUNGROUP} ${CONFDIR}
+ [ -d ${STATEDIR} ] || mkdir -p ${STATEDIR} \
+ || die "${ME}: could not create state directory ${STATEDIR} - exiting."
+ chown -R ${RUNUSER}:${RUNGROUP} ${STATEDIR}
+
+ for d in $START_DAEMONS
+ do
+ echo -n "${ME}: Starting ${d} ... "
+ proc_pid=$(pidof ${d})
+ pidfile=${STATEDIR}/${d}.pid
+ file_pid=
+ if [ -f "${pidfile}" ]
+ then
+ file_pid=$(cat ${pidfile})
+ if [ -n "${file_pid}" ]
+ then
+ echo -n "found old pid file entry ${file_pid} ... "
+ fi
+ fi
+ if [ -n "${proc_pid}" ]
+ then
+ echo -n "found ${d} running (${proc_pid}) - skipping ${d}."
+ if [ "${proc_pid}" != "${file_pid}" ]
+ then
+ if echo "${proc_pid}" > ${pidfile}
+ then
+ chown ${RUNUSER}:${RUNGROUP} ${pidfile}
+ echo -n " Wrote ${proc_pid} to pidfile."
+ else
+ echo -n " Failed to write ${proc_pid} to pidfile."
+ fi
+ fi
+ elif rm -f "${pidfile}"
+ then
+ if [ "${d}" = "watchquagga" ]
+ then
+ "${BINDIR}/${d}" \
+ ${WATCHQUAGGA_FLAGS} \
+ "${WATCHQUAGGA_CMD}" \
+ ${WATCHQUAGGA_DAEMONS}
+ status=$?
+ else
+ "${BINDIR}/${d}" ${DAEMON_FLAGS}
+ status=$?
+ fi
+ if [ $status -eq 0 ]
+ then
+ echo -n "done."
+ else
+ echo -n "failed."
+ fi
+ else
+ echo -n " failed to remove pidfile."
+ fi
+ echo
+ done
+fi
--- /dev/null
+password zebra
+!
+access-list vty permit 127.0.0.0/8
+access-list vty deny any
+!
+line vty
+ access-class vty
--- /dev/null
+#!/bin/sh /etc/rc.common
+# Copyright (C) 2006 OpenWrt.org
+
+START=60
+start() {
+ /usr/sbin/quagga.init start
+}
+
+stop() {
+ /usr/sbin/quagga.init stop
+}
--- /dev/null
+Add definitions for IPCTL_FORWARDING and IP6CTL_FORWARDING.
+
+Inspired from
+http://svn.gnumonks.org/trunk/grouter/build/src/quagga/quagga/quagga-0.99.1-forward_sysctl-2.6.14.patch
+
+Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
+
+--- a/zebra/ipforward_sysctl.c
++++ b/zebra/ipforward_sysctl.c
+@@ -31,6 +31,15 @@
+
+ #define MIB_SIZ 4
+
++/* Fix for recent (2.6.14) kernel headers */
++#ifndef IPCTL_FORWARDING
++#define IPCTL_FORWARDING NET_IPV4_FORWARD
++#endif
++
++#ifndef IP6CTL_FORWARDING
++#define IP6CTL_FORWARDING NET_IPV6_FORWARDING
++#endif
++
+ extern struct zebra_privs_t zserv_privs;
+
+ /* IPv4 forwarding control MIB. */
--- /dev/null
+--- a/lib/log.c
++++ b/lib/log.c
+@@ -929,13 +929,19 @@ proto_redistnum(int afi, const char *s)
+ return ZEBRA_ROUTE_STATIC;
+ else if (strncmp (s, "r", 1) == 0)
+ return ZEBRA_ROUTE_RIP;
+- else if (strncmp (s, "o", 1) == 0)
++ else if (strncmp (s, "os", 2) == 0)
+ return ZEBRA_ROUTE_OSPF;
+ else if (strncmp (s, "i", 1) == 0)
+ return ZEBRA_ROUTE_ISIS;
+ else if (strncmp (s, "bg", 2) == 0)
+ return ZEBRA_ROUTE_BGP;
+- else if (strncmp (s, "ba", 2) == 0)
++ else if (strncmp (s, "h", 1) == 0)
++ return ZEBRA_ROUTE_HSLS;
++ else if (strncmp (s, "ol", 2) == 0)
++ return ZEBRA_ROUTE_OLSR;
++ else if (strncmp (s, "bat", 3) == 0)
++ return ZEBRA_ROUTE_BATMAN;
++ else if (strncmp (s, "bab", 3) == 0)
+ return ZEBRA_ROUTE_BABEL;
+ }
+ if (afi == AFI_IP6)
+@@ -948,13 +954,19 @@ proto_redistnum(int afi, const char *s)
+ return ZEBRA_ROUTE_STATIC;
+ else if (strncmp (s, "r", 1) == 0)
+ return ZEBRA_ROUTE_RIPNG;
+- else if (strncmp (s, "o", 1) == 0)
++ else if (strncmp (s, "os", 2) == 0)
+ return ZEBRA_ROUTE_OSPF6;
+ else if (strncmp (s, "i", 1) == 0)
+ return ZEBRA_ROUTE_ISIS;
+ else if (strncmp (s, "bg", 2) == 0)
+ return ZEBRA_ROUTE_BGP;
+- else if (strncmp (s, "ba", 2) == 0)
++ else if (strncmp (s, "h", 1) == 0)
++ return ZEBRA_ROUTE_HSLS;
++ else if (strncmp (s, "ol", 2) == 0)
++ return ZEBRA_ROUTE_OLSR;
++ else if (strncmp (s, "bat", 3) == 0)
++ return ZEBRA_ROUTE_BATMAN;
++ else if (strncmp (s, "bab", 3) == 0)
+ return ZEBRA_ROUTE_BABEL;
+ }
+ return -1;
+--- a/lib/route_types.txt
++++ b/lib/route_types.txt
+@@ -51,13 +51,9 @@ ZEBRA_ROUTE_OSPF, ospf, ospfd
+ ZEBRA_ROUTE_OSPF6, ospf6, ospf6d, 'O', 0, 1, "OSPFv6"
+ ZEBRA_ROUTE_ISIS, isis, isisd, 'I', 1, 1, "IS-IS"
+ ZEBRA_ROUTE_BGP, bgp, bgpd, 'B', 1, 1, "BGP"
+-# HSLS and OLSR both are AFI independent (so: 1, 1), however
+-# we want to disable for them for general Quagga distribution.
+-# This at least makes it trivial for users of these protocols
+-# to 'switch on' redist support (direct numeric entry remaining
+-# possible).
+-ZEBRA_ROUTE_HSLS, hsls, hslsd, 'H', 0, 0, "HSLS"
+-ZEBRA_ROUTE_OLSR, olsr, olsrd, 'o', 0, 0, "OLSR"
++ZEBRA_ROUTE_HSLS, hsls, hslsd, 'H', 1, 1, "HSLS"
++ZEBRA_ROUTE_OLSR, olsr, olsrd, 'o', 1, 1, "OLSR"
++ZEBRA_ROUTE_BATMAN, batman, batmand,'b', 1, 1, "BATMAN"
+ ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, "Babel"
+
+ ## help strings
+@@ -72,5 +68,6 @@ ZEBRA_ROUTE_OSPF6, "Open Shortest Path
+ ZEBRA_ROUTE_ISIS, "Intermediate System to Intermediate System (IS-IS)"
+ ZEBRA_ROUTE_BGP, "Border Gateway Protocol (BGP)"
+ ZEBRA_ROUTE_HSLS, "Hazy-Sighted Link State Protocol (HSLS)"
+-ZEBRA_ROUTE_OLSR, "Optimised Link State Routing (OLSR)"
++ZEBRA_ROUTE_OLSR, "Optimized Link State Routing (OLSR)"
++ZEBRA_ROUTE_BATMAN, "Better Approach to Mobile Ad-Hoc Networking (BATMAN)"
+ ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)"
+--- a/ripd/rip_zebra.c
++++ b/ripd/rip_zebra.c
+@@ -206,9 +206,12 @@ static struct {
+ {ZEBRA_ROUTE_KERNEL, 1, "kernel"},
+ {ZEBRA_ROUTE_CONNECT, 1, "connected"},
+ {ZEBRA_ROUTE_STATIC, 1, "static"},
+- {ZEBRA_ROUTE_OSPF, 1, "ospf"},
++ {ZEBRA_ROUTE_OSPF, 2, "ospf"},
+ {ZEBRA_ROUTE_BGP, 2, "bgp"},
+- {ZEBRA_ROUTE_BABEL, 2, "babel"},
++ {ZEBRA_ROUTE_HSLS, 1, "hsls"},
++ {ZEBRA_ROUTE_OLSR, 2, "olsr"},
++ {ZEBRA_ROUTE_BATMAN, 3, "batman"},
++ {ZEBRA_ROUTE_BABEL, 3, "babel"},
+ {0, 0, NULL}
+ };
+
+--- a/ripngd/ripng_zebra.c
++++ b/ripngd/ripng_zebra.c
+@@ -216,9 +216,12 @@ static struct {
+ {ZEBRA_ROUTE_KERNEL, 1, "kernel"},
+ {ZEBRA_ROUTE_CONNECT, 1, "connected"},
+ {ZEBRA_ROUTE_STATIC, 1, "static"},
+- {ZEBRA_ROUTE_OSPF6, 1, "ospf6"},
++ {ZEBRA_ROUTE_OSPF6, 2, "ospf6"},
+ {ZEBRA_ROUTE_BGP, 2, "bgp"},
+- {ZEBRA_ROUTE_BABEL, 2, "babel"},
++ {ZEBRA_ROUTE_HSLS, 1, "hsls"},
++ {ZEBRA_ROUTE_OLSR, 2, "olsr"},
++ {ZEBRA_ROUTE_BATMAN, 3, "batman"},
++ {ZEBRA_ROUTE_BABEL, 3, "babel"},
+ {0, 0, NULL}
+ };
+
+--- a/zebra/rt_netlink.c
++++ b/zebra/rt_netlink.c
+@@ -1623,6 +1623,9 @@ netlink_route_multipath (int cmd, struct
+ addattr_l (&req.n, sizeof req, RTA_PREFSRC,
+ &nexthop->src.ipv4, bytelen);
+
++ if (rib->type == ZEBRA_ROUTE_OLSR)
++ req.r.rtm_scope = RT_SCOPE_LINK;
++
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("netlink_route_multipath() (single hop): "
+ "nexthop via if %u", nexthop->ifindex);
+--- a/zebra/zebra_rib.c
++++ b/zebra/zebra_rib.c
+@@ -67,6 +67,9 @@ static const struct
+ [ZEBRA_ROUTE_OSPF6] = {ZEBRA_ROUTE_OSPF6, 110},
+ [ZEBRA_ROUTE_ISIS] = {ZEBRA_ROUTE_ISIS, 115},
+ [ZEBRA_ROUTE_BGP] = {ZEBRA_ROUTE_BGP, 20 /* IBGP is 200. */},
++ [ZEBRA_ROUTE_HSLS] = {ZEBRA_ROUTE_HSLS, 0},
++ [ZEBRA_ROUTE_OLSR] = {ZEBRA_ROUTE_OLSR, 0},
++ [ZEBRA_ROUTE_BATMAN] = {ZEBRA_ROUTE_BATMAN, 0},
+ [ZEBRA_ROUTE_BABEL] = {ZEBRA_ROUTE_BABEL, 95},
+ /* no entry/default: 150 */
+ };
+@@ -403,6 +406,18 @@ nexthop_active_ipv4 (struct rib *rib, st
+ }
+ return 0;
+ }
++ else if (match->type == ZEBRA_ROUTE_OLSR)
++ {
++ for (newhop = match->nexthop; newhop; newhop = newhop->next)
++ if (CHECK_FLAG (newhop->flags, NEXTHOP_FLAG_FIB)
++ && newhop->type == NEXTHOP_TYPE_IFINDEX)
++ {
++ if (nexthop->type == NEXTHOP_TYPE_IPV4)
++ nexthop->ifindex = newhop->ifindex;
++ return 1;
++ }
++ return 0;
++ }
+ else
+ {
+ return 0;
+@@ -507,6 +522,18 @@ nexthop_active_ipv6 (struct rib *rib, st
+ }
+ return 0;
+ }
++ else if (match->type == ZEBRA_ROUTE_OLSR)
++ {
++ for (newhop = match->nexthop; newhop; newhop = newhop->next)
++ if (CHECK_FLAG (newhop->flags, NEXTHOP_FLAG_FIB)
++ && newhop->type == NEXTHOP_TYPE_IFINDEX)
++ {
++ if (nexthop->type == NEXTHOP_TYPE_IPV6)
++ nexthop->ifindex = newhop->ifindex;
++ return 1;
++ }
++ return 0;
++ }
+ else
+ {
+ return 0;
+@@ -1236,6 +1263,8 @@ static const u_char meta_queue_map[ZEBRA
+ [ZEBRA_ROUTE_ISIS] = 2,
+ [ZEBRA_ROUTE_BGP] = 3,
+ [ZEBRA_ROUTE_HSLS] = 4,
++ [ZEBRA_ROUTE_OLSR] = 4,
++ [ZEBRA_ROUTE_BATMAN] = 4,
+ [ZEBRA_ROUTE_BABEL] = 2,
+ };
+
+--- a/zebra/zebra_snmp.c
++++ b/zebra/zebra_snmp.c
+@@ -251,6 +251,12 @@ proto_trans(int type)
+ return 1; /* shouldn't happen */
+ case ZEBRA_ROUTE_BGP:
+ return 14; /* bgp */
++ case ZEBRA_ROUTE_HSLS:
++ return 1; /* other */
++ case ZEBRA_ROUTE_OLSR:
++ return 1; /* other */
++ case ZEBRA_ROUTE_BATMAN:
++ return 1; /* other */
+ default:
+ return 1; /* other */
+ }
+--- a/zebra/zebra_vty.c
++++ b/zebra/zebra_vty.c
+@@ -558,7 +558,10 @@ vty_show_ip_route_detail (struct vty *vt
+ || rib->type == ZEBRA_ROUTE_OSPF
+ || rib->type == ZEBRA_ROUTE_BABEL
+ || rib->type == ZEBRA_ROUTE_ISIS
+- || rib->type == ZEBRA_ROUTE_BGP)
++ || rib->type == ZEBRA_ROUTE_BGP
++ || rib->type == ZEBRA_ROUTE_HSLS
++ || rib->type == ZEBRA_ROUTE_OLSR
++ || rib->type == ZEBRA_ROUTE_BATMAN)
+ {
+ time_t uptime;
+ struct tm *tm;
+@@ -777,7 +780,10 @@ vty_show_ip_route (struct vty *vty, stru
+ || rib->type == ZEBRA_ROUTE_OSPF
+ || rib->type == ZEBRA_ROUTE_BABEL
+ || rib->type == ZEBRA_ROUTE_ISIS
+- || rib->type == ZEBRA_ROUTE_BGP)
++ || rib->type == ZEBRA_ROUTE_BGP
++ || rib->type == ZEBRA_ROUTE_HSLS
++ || rib->type == ZEBRA_ROUTE_OLSR
++ || rib->type == ZEBRA_ROUTE_BATMAN)
+ {
+ time_t uptime;
+ struct tm *tm;
+@@ -1536,7 +1542,10 @@ vty_show_ipv6_route_detail (struct vty *
+ || rib->type == ZEBRA_ROUTE_OSPF6
+ || rib->type == ZEBRA_ROUTE_BABEL
+ || rib->type == ZEBRA_ROUTE_ISIS
+- || rib->type == ZEBRA_ROUTE_BGP)
++ || rib->type == ZEBRA_ROUTE_BGP
++ || rib->type == ZEBRA_ROUTE_HSLS
++ || rib->type == ZEBRA_ROUTE_OLSR
++ || rib->type == ZEBRA_ROUTE_BATMAN)
+ {
+ time_t uptime;
+ struct tm *tm;
+@@ -1716,7 +1725,10 @@ vty_show_ipv6_route (struct vty *vty, st
+ || rib->type == ZEBRA_ROUTE_OSPF6
+ || rib->type == ZEBRA_ROUTE_BABEL
+ || rib->type == ZEBRA_ROUTE_ISIS
+- || rib->type == ZEBRA_ROUTE_BGP)
++ || rib->type == ZEBRA_ROUTE_BGP
++ || rib->type == ZEBRA_ROUTE_HSLS
++ || rib->type == ZEBRA_ROUTE_OLSR
++ || rib->type == ZEBRA_ROUTE_BATMAN)
+ {
+ time_t uptime;
+ struct tm *tm;
--- /dev/null
+--- a/lib/log.c
++++ b/lib/log.c
+@@ -941,6 +941,8 @@ proto_redistnum(int afi, const char *s)
+ return ZEBRA_ROUTE_OLSR;
+ else if (strncmp (s, "bat", 3) == 0)
+ return ZEBRA_ROUTE_BATMAN;
++ else if (strncmp (s, "bmx", 3) == 0)
++ return ZEBRA_ROUTE_BMX6;
+ else if (strncmp (s, "bab", 3) == 0)
+ return ZEBRA_ROUTE_BABEL;
+ }
+@@ -966,6 +968,8 @@ proto_redistnum(int afi, const char *s)
+ return ZEBRA_ROUTE_OLSR;
+ else if (strncmp (s, "bat", 3) == 0)
+ return ZEBRA_ROUTE_BATMAN;
++ else if (strncmp (s, "bmx", 3) == 0)
++ return ZEBRA_ROUTE_BMX6;
+ else if (strncmp (s, "bab", 3) == 0)
+ return ZEBRA_ROUTE_BABEL;
+ }
+--- a/lib/route_types.txt
++++ b/lib/route_types.txt
+@@ -54,6 +54,7 @@ ZEBRA_ROUTE_BGP, bgp, bgpd,
+ ZEBRA_ROUTE_HSLS, hsls, hslsd, 'H', 1, 1, "HSLS"
+ ZEBRA_ROUTE_OLSR, olsr, olsrd, 'o', 1, 1, "OLSR"
+ ZEBRA_ROUTE_BATMAN, batman, batmand,'b', 1, 1, "BATMAN"
++ZEBRA_ROUTE_BMX6, bmx6, bmx6, 'x', 1, 1, "BMX6"
+ ZEBRA_ROUTE_BABEL, babel, babeld, 'A', 1, 1, "Babel"
+
+ ## help strings
+@@ -70,4 +71,5 @@ ZEBRA_ROUTE_BGP, "Border Gateway Prot
+ ZEBRA_ROUTE_HSLS, "Hazy-Sighted Link State Protocol (HSLS)"
+ ZEBRA_ROUTE_OLSR, "Optimized Link State Routing (OLSR)"
+ ZEBRA_ROUTE_BATMAN, "Better Approach to Mobile Ad-Hoc Networking (BATMAN)"
++ZEBRA_ROUTE_BMX6, "BMX6 networking protocol"
+ ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)"
+--- a/ripd/rip_zebra.c
++++ b/ripd/rip_zebra.c
+@@ -211,6 +211,7 @@ static struct {
+ {ZEBRA_ROUTE_HSLS, 1, "hsls"},
+ {ZEBRA_ROUTE_OLSR, 2, "olsr"},
+ {ZEBRA_ROUTE_BATMAN, 3, "batman"},
++ {ZEBRA_ROUTE_BMX6, 3, "bmx6"},
+ {ZEBRA_ROUTE_BABEL, 3, "babel"},
+ {0, 0, NULL}
+ };
+--- a/ripngd/ripng_zebra.c
++++ b/ripngd/ripng_zebra.c
+@@ -221,6 +221,7 @@ static struct {
+ {ZEBRA_ROUTE_HSLS, 1, "hsls"},
+ {ZEBRA_ROUTE_OLSR, 2, "olsr"},
+ {ZEBRA_ROUTE_BATMAN, 3, "batman"},
++ {ZEBRA_ROUTE_BMX6, 3, "bmx6"},
+ {ZEBRA_ROUTE_BABEL, 3, "babel"},
+ {0, 0, NULL}
+ };
+--- a/zebra/zebra_rib.c
++++ b/zebra/zebra_rib.c
+@@ -70,6 +70,7 @@ static const struct
+ [ZEBRA_ROUTE_HSLS] = {ZEBRA_ROUTE_HSLS, 0},
+ [ZEBRA_ROUTE_OLSR] = {ZEBRA_ROUTE_OLSR, 0},
+ [ZEBRA_ROUTE_BATMAN] = {ZEBRA_ROUTE_BATMAN, 0},
++ [ZEBRA_ROUTE_BMX6] = {ZEBRA_ROUTE_BMX6, 0},
+ [ZEBRA_ROUTE_BABEL] = {ZEBRA_ROUTE_BABEL, 95},
+ /* no entry/default: 150 */
+ };
+@@ -1265,6 +1266,7 @@ static const u_char meta_queue_map[ZEBRA
+ [ZEBRA_ROUTE_HSLS] = 4,
+ [ZEBRA_ROUTE_OLSR] = 4,
+ [ZEBRA_ROUTE_BATMAN] = 4,
++ [ZEBRA_ROUTE_BMX6] = 4,
+ [ZEBRA_ROUTE_BABEL] = 2,
+ };
+
+--- a/zebra/zebra_snmp.c
++++ b/zebra/zebra_snmp.c
+@@ -257,6 +257,8 @@ proto_trans(int type)
+ return 1; /* other */
+ case ZEBRA_ROUTE_BATMAN:
+ return 1; /* other */
++ case ZEBRA_ROUTE_BMX6:
++ return 1; /* other */
+ default:
+ return 1; /* other */
+ }
+--- a/zebra/zebra_vty.c
++++ b/zebra/zebra_vty.c
+@@ -561,7 +561,8 @@ vty_show_ip_route_detail (struct vty *vt
+ || rib->type == ZEBRA_ROUTE_BGP
+ || rib->type == ZEBRA_ROUTE_HSLS
+ || rib->type == ZEBRA_ROUTE_OLSR
+- || rib->type == ZEBRA_ROUTE_BATMAN)
++ || rib->type == ZEBRA_ROUTE_BATMAN
++ || rib->type == ZEBRA_ROUTE_BMX6)
+ {
+ time_t uptime;
+ struct tm *tm;
+@@ -783,7 +784,8 @@ vty_show_ip_route (struct vty *vty, stru
+ || rib->type == ZEBRA_ROUTE_BGP
+ || rib->type == ZEBRA_ROUTE_HSLS
+ || rib->type == ZEBRA_ROUTE_OLSR
+- || rib->type == ZEBRA_ROUTE_BATMAN)
++ || rib->type == ZEBRA_ROUTE_BATMAN
++ || rib->type == ZEBRA_ROUTE_BMX6)
+ {
+ time_t uptime;
+ struct tm *tm;
+@@ -1545,7 +1547,8 @@ vty_show_ipv6_route_detail (struct vty *
+ || rib->type == ZEBRA_ROUTE_BGP
+ || rib->type == ZEBRA_ROUTE_HSLS
+ || rib->type == ZEBRA_ROUTE_OLSR
+- || rib->type == ZEBRA_ROUTE_BATMAN)
++ || rib->type == ZEBRA_ROUTE_BATMAN
++ || rib->type == ZEBRA_ROUTE_BMX6)
+ {
+ time_t uptime;
+ struct tm *tm;
+@@ -1728,7 +1731,8 @@ vty_show_ipv6_route (struct vty *vty, st
+ || rib->type == ZEBRA_ROUTE_BGP
+ || rib->type == ZEBRA_ROUTE_HSLS
+ || rib->type == ZEBRA_ROUTE_OLSR
+- || rib->type == ZEBRA_ROUTE_BATMAN)
++ || rib->type == ZEBRA_ROUTE_BATMAN
++ || rib->type == ZEBRA_ROUTE_BMX6)
+ {
+ time_t uptime;
+ struct tm *tm;
--- /dev/null
+--- a/vtysh/extract.pl.in
++++ b/vtysh/extract.pl.in
+@@ -63,7 +63,7 @@ $ignore{'"show history"'} = "ignore";
+ foreach (@ARGV) {
+ $file = $_;
+
+- open (FH, "cpp -DHAVE_CONFIG_H -DVTYSH_EXTRACT_PL -DHAVE_IPV6 -I@top_builddir@ -I@srcdir@/ -I@srcdir@/.. -I@top_srcdir@/lib -I@top_srcdir@/isisd/topology @SNMP_INCLUDES@ @CPPFLAGS@ $file |");
++ open (FH, "@CPP@ -DHAVE_CONFIG_H -DVTYSH_EXTRACT_PL -DHAVE_IPV6 -I@top_builddir@ -I@srcdir@/ -I@srcdir@/.. -I@top_srcdir@/lib -I@top_srcdir@/isisd/topology @SNMP_INCLUDES@ @CPPFLAGS@ $file |");
+ local $/; undef $/;
+ $line = <FH>;
+ close (FH);
--- /dev/null
+--- a/bgpd/bgp_network.c
++++ b/bgpd/bgp_network.c
+@@ -193,8 +193,7 @@ bgp_accept (struct thread *thread)
+ peer->fd = bgp_sock;
+ peer->status = Active;
+ peer->local_id = peer1->local_id;
+- peer->v_holdtime = peer1->v_holdtime;
+- peer->v_keepalive = peer1->v_keepalive;
++ peer->v_holdtime = BGP_LARGE_HOLDTIME;
+
+ /* Make peer's address string. */
+ sockunion2str (&su, buf, SU_ADDRSTRLEN);
+--- a/bgpd/bgpd.h
++++ b/bgpd/bgpd.h
+@@ -718,6 +718,7 @@ struct bgp_nlri
+ /* BGP timers default value. */
+ #define BGP_INIT_START_TIMER 5
+ #define BGP_ERROR_START_TIMER 30
++#define BGP_LARGE_HOLDTIME 240
+ #define BGP_DEFAULT_HOLDTIME 180
+ #define BGP_DEFAULT_KEEPALIVE 60
+ #define BGP_DEFAULT_ASORIGINATE 15
--- /dev/null
+--- a/lib/command.c
++++ b/lib/command.c
+@@ -2601,6 +2601,13 @@ DEFUN (config_write_file,
+ VTY_NEWLINE);
+ goto finished;
+ }
++
++#if 0
++ /* This code fails on UNION MOUNTs and similar filesystems if the
++ * config file is still on the RO layer. Hardlinks across layers
++ * will not work and cause quagga to fail saving the configuration...
++ * should use rename() to move files around...
++ */
+ if (link (config_file, config_file_sav) != 0)
+ {
+ vty_out (vty, "Can't backup old configuration file %s.%s", config_file_sav,
+@@ -2614,7 +2621,23 @@ DEFUN (config_write_file,
+ VTY_NEWLINE);
+ goto finished;
+ }
++#else
++ /* And this is the code that hopefully does work */
++ if (rename (config_file, config_file_sav) != 0)
++ {
++ vty_out (vty, "Can't backup old configuration file %s.%s", config_file_sav,
++ VTY_NEWLINE);
++ goto finished;
++ }
++ sync ();
++#endif
++
++#if 0
++ /* same here. Please no cross-filesystem hardlinks... */
+ if (link (config_file_tmp, config_file) != 0)
++#else
++ if (rename (config_file_tmp, config_file) != 0)
++#endif
+ {
+ vty_out (vty, "Can't save configuration file %s.%s", config_file,
+ VTY_NEWLINE);
--- /dev/null
+From: Josh Karlin <karlinjf@cs.unm.edu>
+Date: Mon, 18 Aug 2008 13:17:21 +0000 (+0100)
+Subject: [bgp] Add support for Pretty-Good BGP
+X-Git-Url: http://git.ozo.com/?p=quagga-pgbg.git;a=commitdiff_plain;h=c2ee55705cad607f4b86ff143f7af92d538dc946
+
+[bgp] Add support for Pretty-Good BGP
+
+2008-7-7 Josh Karlin <karlinjf@cs.unm.edu>
+
+ * bgpd/bgp_pgbgp.c: Added file
+ * bgpd/bgp_pgbgp.h: Added file
+ * bgpd/Makefile.am: Added bgp_pgbgp.h and bgp_pgbgp.c
+ * bgpd/bgp_aspath.h: Externed the hash of as paths (ashash)
+ * bgpd/bgp_route.c: . Added PGBGP depref check to decision process.
+ . Informs PGBGP of new updates and selected routes
+ . Added anomaly status for show ip bgp
+ . Added PGBGP commands
+ * bgpd/bgp_route.h: Added suspicious route flags
+ * bgpd/bgp_table.h: Added PGBGP history pointer to struct bgp_node
+ * bgpd/bgpd.h: Defined BGP_CONFIG_PGBGP
+ * lib/hash.c: Added "hash_iterate_until" to be able to break out
+ * lib/hash.h: Definition for "hash_iterate_until"
+ * lib/memtypes.c: Added PGBGP memory types
+---
+
+--- a/bgpd/Makefile.am
++++ b/bgpd/Makefile.am
+@@ -15,14 +15,14 @@ libbgp_a_SOURCES = \
+ bgp_debug.c bgp_route.c bgp_zebra.c bgp_open.c bgp_routemap.c \
+ bgp_packet.c bgp_network.c bgp_filter.c bgp_regex.c bgp_clist.c \
+ bgp_dump.c bgp_snmp.c bgp_ecommunity.c bgp_mplsvpn.c bgp_nexthop.c \
+- bgp_damp.c bgp_table.c bgp_advertise.c bgp_vty.c bgp_mpath.c
++ bgp_damp.c bgp_table.c bgp_advertise.c bgp_vty.c bgp_mpath.c bgp_pgbgp.c
+
+ noinst_HEADERS = \
+ bgp_aspath.h bgp_attr.h bgp_community.h bgp_debug.h bgp_fsm.h \
+ bgp_network.h bgp_open.h bgp_packet.h bgp_regex.h bgp_route.h \
+ bgpd.h bgp_filter.h bgp_clist.h bgp_dump.h bgp_zebra.h \
+ bgp_ecommunity.h bgp_mplsvpn.h bgp_nexthop.h bgp_damp.h bgp_table.h \
+- bgp_advertise.h bgp_snmp.h bgp_vty.h bgp_mpath.h
++ bgp_advertise.h bgp_snmp.h bgp_vty.h bgp_mpath.h bgp_pgbgp.h
+
+ bgpd_SOURCES = bgp_main.c
+ bgpd_LDADD = libbgp.a ../lib/libzebra.la @LIBCAP@ @LIBM@
+--- /dev/null
++++ b/bgpd/bgp_pgbgp.c
+@@ -0,0 +1,2401 @@
++/*
++ BGP Pretty Good BGP
++ Copyright (C) 2008 University of New Mexico (Josh Karlin)
++
++This file is part of GNU Zebra.
++
++GNU Zebra is free software; you can redistribute it and/or modify it
++under the terms of the GNU General Public License as published by the
++Free Software Foundation; either version 2, or (at your option) any
++later version.
++
++GNU Zebra is distributed in the hope that it will be useful, but
++WITHOUT ANY WARRANTY; without even the implied warranty of
++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++General Public License for more details.
++
++You should have received a copy of the GNU General Public License
++along with GNU Zebra; see the file COPYING. If not, write to the Free
++Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
++02111-1307, USA.
++*/
++
++/*
++ Quagga based Pretty Good BGP:
++
++ Summary
++ -------
++ Pretty Good BGP (PGBGP) is a soft security enhancement to BGP.
++ It uses independently collected (therefore completely distributed)
++ historical routing information to determine network topology and
++ prefix ownership. Abberations to the historical database are considered
++ anomalous and avoided when possible.
++
++ What PGBGP can protect against: prefix hijacks, sub-prefix hijacks, and
++ spoofed edges.
++
++ Further reading is available at http://cs.unm.edu/~karlinjf/pgbgp/
++
++ Route updates are forwarded to PGBGP, which determines if the route
++ is anomalous. Anomalous routes are flagged as suspicious and
++ avoided where feasible for 24 hours. If the anomalous
++ characteristic is still in the RIB after 24 hours, consider it valid
++ and enter it into the normal database.
++
++ Cases for anomalous routes
++ --------------------------
++ case 1) New origin AS - prefix pair (one not recently seen in the RIB):
++ response) label the route with BGP_INFO_SUSPICIOUS_O and avoid for 24 hours if possible
++
++ case 2) New edge in path (one not recently seen in the RIB):
++ response) label the route with BGP_INFO_SUSPICIOUS_E and avoid for 24 hours
++ if possible
++
++ case 3) New prefix that is a sub-prefix of a prefix in recent history
++ and that path differs from the current less-specific's path
++ response) label the sub-prefix routes with BGP_INFO_IGNORED_P and
++ prevent it from entering FIB for 24 hours
++ response) label the super-net routes from the same next-hop as BGP_INFO_SUSPICIOUS_P
++ and try to avoid it for 24 hours if possible
++ response) while no super-net route is selected, remove the BGP_INFO_IGNORED_P flags
++
++
++ Normal Database (history)
++ -------------------------
++ Recently Seen) A route characteristic (edge, prefix/origin pair, prefix)
++ that has resided within the RIB within the last X hours
++ where X is user defined for each characteristic.
++ Storage) Prefix and Origin history are stored in bgp_node structs with the
++ "hist" pointer.
++ Edge information is stored in a separate hash table, where the edge
++ is the key to the hash.
++ Updates) The history's primary function is the keep track of when each route
++ characteristic was last seen. For each route announcement, update
++ the history's 'last seen' time. Periodically run the garbage collector
++ which updates 'last seen' times for objects currently in the RIB.
++
++ Garbage Collection
++ ------------------
++ Periodically the garbage collector (gc) is called to remove stale history
++ information and update the lastSeen time of objects that reside in the RIB
++ at the time of collection. This is relatively expensive as it walks
++ the RIB as well as the list of AS paths.
++
++ What is removed) Objects that have not been seen in the RIB within a user-defined
++ time.
++ Suspicious objcets that are 24 hours old that have not been in the RIB
++ since the last collection.
++
++ Reuse Priority Queue
++ --------------------
++ After 24 hours, routes that are flagged as suspicious have the flags removed.
++ This is not run on a timer. Instead, for each update that PGBGP is informed of,
++ it checks the reuse queue to determine if any routes need to be updated.
++
++*/
++
++
++/*
++ Things that must be ensured:
++ . GC updates lastSeen so it must be called at least twice as often as the lowest BUFFER_TIME
++ . GC should be called at least twice per day
++ . Delay times must be shorter than history window lengths
++*/
++
++
++/*
++ Changes made to original PGBGP thinking
++ . Don't check for things in the RIB all of the time, periodically
++ update the lastSeen values and just use lastSeen
++*/
++
++/*
++ Changes made to original protocol
++ . sub-prefixes are only ignored while the super-net has a selected
++ route and it's non-anomalous (not to a neighbor that announced
++ the sub-prefix)
++
++ . At point of reuse, don't delete the item if it's not in the RIB.
++ delete it if it hasn't been in the RIB since the last storage.
++ This saves a lot of processing time for new edges
++
++ . Changed heuristic from "if new sub-prefix and trusted AS on path
++ then it's okay" to "if new sub-prefix and same path is used to reach
++ super-prefix, then it's okay". Might be better to change to "if old
++ path is prefix of new path, then okay"
++*/
++
++#include <zebra.h>
++#include <math.h>
++
++#include "prefix.h"
++#include "memory.h"
++#include "command.h"
++#include "log.h"
++#include "pqueue.h"
++#include "table.h"
++#include "hash.h"
++#include "str.h"
++
++#include "bgpd/bgpd.h"
++#include "bgpd/bgp_aspath.h"
++#include "bgpd/bgp_pgbgp.h"
++#include "bgpd/bgp_table.h"
++#include "bgpd/bgp_route.h"
++#include "bgpd/bgp_attr.h"
++#include "bgpd/bgp_advertise.h"
++
++
++#define true 1
++#define false 0
++
++struct hash * ashash;
++
++static void *edge_hash_alloc (void *arg);
++static unsigned int edge_key_make (void *p);
++static int edge_cmp (const void *arg1, const void *args);
++
++// Helper Functions
++static struct bgp_pgbgp_pathSet bgp_pgbgp_pathOrigin (struct aspath *);
++static int bgp_pgbgp_pathLength (struct aspath *asp);
++static int bgp_pgbgp_gc (struct bgp_table *);
++static int bgp_pgbgp_clean (struct bgp_table *);
++static int bgp_pgbgp_reuse (time_t);
++static struct bgp_node *findSuper (struct bgp_table *table, struct prefix *p,
++ time_t t_now);
++static int bgp_pgbgp_store (struct bgp_table *table);
++static int bgp_pgbgp_restore (void);
++static struct bgp_info *bgp_pgbgp_selected (struct bgp_node *node);
++static int originInRIB (struct bgp_node *node, struct bgp_pgbgp_origin *origin);
++static int prefixInRIB (struct bgp_node *node, struct bgp_pgbgp_prefix *prefix);
++static int edgeInRIB (struct bgp_pgbgp_edge *e);
++
++// MOAS Functions
++static void bgp_pgbgp_logOriginAnomaly (as_t asn, struct bgp_node *rn,
++ struct attr *);
++static int bgp_pgbgp_reuseOrigin (struct bgp_pgbgp_r_origin);
++static void bgp_pgbgp_cleanHistTable (struct bgp_table *);
++static int bgp_pgbgp_garbageCollectHistTable (struct bgp_table *);
++static void bgp_pgbgp_storeHistTable (struct bgp_table *table, FILE * file);
++static int bgp_pgbgp_updateOrigin (struct bgp_pgbgp_hist *, struct bgp_info *,
++ struct attr *, struct bgp_node *, time_t, int);
++
++
++// Sub-Prefix Hijack Detector Functions
++static int bgp_pgbgp_shouldIgnore (struct bgp_node *super, struct bgp_info *selected);
++static void bgp_pgbgp_logSubprefixAnomaly (as_t asn, struct bgp_node *rn,
++ struct attr *, struct bgp_node *super);
++static int bgp_pgbgp_reusePrefix (struct bgp_pgbgp_r_prefix);
++static int bgp_pgbgp_updatePrefix (struct bgp_pgbgp_hist *hist, struct bgp_node *,
++ struct bgp_info *, struct attr *,
++ struct bgp_node *, time_t, int);
++
++
++// Spoofed Edge Detector Functions
++static void bgp_pgbgp_cleanEdges (void);
++static void bgp_pgbgp_logEdgeAnomaly (struct bgp_node *rn, struct attr *,
++ struct edge *edge);
++static int bgp_pgbgp_reuseEdge (struct bgp_pgbgp_r_edge);
++static void bgp_pgbgp_storeEdges (struct bgp_table *, FILE *);
++static int bgp_pgbgp_garbageCollectEdges (struct bgp_table *);
++static int bgp_pgbgp_updateEdge (struct bgp_pgbgp_hist *hist, struct bgp_info *,
++ struct attr *, struct bgp_node *, time_t, int);
++static int bgp_pgbgp_restoreEdge (FILE * file);
++static void bgp_pgbgp_storeEdges (struct bgp_table *table, FILE * file);
++
++
++
++// New Peer Detector Functions
++static int bgp_pgbgp_updatePeer (struct bgp_info *binfo, time_t now);
++
++
++/* --------------- Global Variables ------------------ */
++struct bgp_pgbgp_config bgp_pgbgp_cfg;
++struct bgp_pgbgp_config *pgbgp = &bgp_pgbgp_cfg;
++/*! --------------- Global Variables ------------------ !*/
++
++/* --------------- VTY (others exist in bgp_route.c) ------------------ */
++
++struct nsearch
++{
++ struct vty *pvty;
++ time_t time;
++ as_t asn;
++};
++
++static void
++edge_neighbor_iterator (struct hash_backet *backet, struct nsearch *pns)
++{
++ struct bgp_pgbgp_edge *hedge = backet->data;
++ if ((hedge->e.a == pns->asn || hedge->e.b == pns->asn)
++ && hedge->e.a != hedge->e.b)
++ {
++ struct vty *vty = pns->pvty;
++ if (hedge->deprefUntil > pns->time)
++ vty_out (pns->pvty, "Untrusted: %d -- %d%s", hedge->e.a, hedge->e.b,
++ VTY_NEWLINE);
++ else
++ vty_out (pns->pvty, "Trusted: %d -- %d%s", hedge->e.a, hedge->e.b,
++ VTY_NEWLINE);
++ }
++}
++
++static int
++bgp_pgbgp_stats_neighbors (struct vty *vty, afi_t afi, safi_t safi, as_t asn)
++{
++ struct nsearch ns;
++ ns.pvty = vty;
++ ns.time = time (NULL);
++ ns.asn = asn;
++
++ hash_iterate (pgbgp->edgeT,
++ (void (*)(struct hash_backet *, void *))
++ edge_neighbor_iterator, &ns);
++ return CMD_SUCCESS;
++}
++
++static int
++bgp_pgbgp_stats_origins (struct vty *vty, afi_t afi, safi_t safi,
++ const char *prefix)
++{
++ struct bgp *bgp;
++ struct bgp_table *table;
++ time_t t_now = time (NULL);
++ bgp = bgp_get_default ();
++ if (bgp == NULL)
++ return CMD_WARNING;
++ if (bgp->rib == NULL)
++ return CMD_WARNING;
++ table = bgp->rib[afi][safi];
++ if (table == NULL)
++ return CMD_WARNING;
++
++ struct prefix p;
++ str2prefix (prefix, &p);
++ struct bgp_node *rn = bgp_node_match (table, &p);
++ vty_out (vty, "%s%s", prefix, VTY_NEWLINE);
++ if (rn)
++ {
++ if (rn->hist)
++ {
++ for (struct bgp_pgbgp_origin * cur = rn->hist->o; cur != NULL;
++ cur = cur->next)
++ {
++ if (cur->deprefUntil > t_now)
++ vty_out (vty, "Untrusted Origin AS: %d%s", cur->originAS,
++ VTY_NEWLINE);
++ else
++ vty_out (vty, "Trusted Origin AS: %d%s", cur->originAS,
++ VTY_NEWLINE);
++ }
++ }
++ bgp_unlock_node (rn);
++ }
++ return CMD_SUCCESS;
++}
++
++static int
++bgp_pgbgp_stats (struct vty *vty, afi_t afi, safi_t safi)
++{
++ struct bgp *bgp;
++ struct bgp_table *table;
++
++
++ bgp = bgp_get_default ();
++ if (bgp == NULL)
++ return CMD_WARNING;
++ if (bgp->rib == NULL)
++ return CMD_WARNING;
++ table = bgp->rib[afi][safi];
++ if (table == NULL)
++ return CMD_WARNING;
++
++ // bgp_pgbgp_store(table);
++
++ // Print out the number of anomalous routes
++ int anomalous = 0;
++ int routes = 0;
++ int num_selected = 0;
++ int num_origin = 0;
++ int num_super = 0;
++ int num_ignored = 0;
++ int num_edge = 0;
++
++ for (struct bgp_node * rn = bgp_table_top (table); rn;
++ rn = bgp_route_next (rn))
++ {
++ for (struct bgp_info * ri = rn->info; ri; ri = ri->next)
++ {
++ routes += 1;
++ if (ANOMALOUS (ri->flags))
++ {
++ anomalous += 1;
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED))
++ num_selected += 1;
++
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_O))
++ num_origin += 1;
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_E))
++ num_edge += 1;
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_P))
++ num_super += 1;
++ if (CHECK_FLAG (ri->flags, BGP_INFO_IGNORED_P))
++ num_ignored += 1;
++ }
++ }
++ }
++
++ vty_out (vty, "%-30s: %10d%s", "Routes in the RIB", routes, VTY_NEWLINE);
++ vty_out (vty, "%-30s: %10d%s", "Anomalous routes in RIB", anomalous,
++ VTY_NEWLINE);
++ vty_out (vty, "%-30s: %10d%s", "Selected anomalous routes", num_selected,
++ VTY_NEWLINE);
++ vty_out (vty, "-----------------------------%s", VTY_NEWLINE);
++ vty_out (vty, "%-30s: %10d%s", "Routes with anomalous origins", num_origin,
++ VTY_NEWLINE);
++ vty_out (vty, "%-30s: %10d%s", "Routes with anomalous edges", num_edge,
++ VTY_NEWLINE);
++ vty_out (vty, "%-30s: %10d%s", "Routes ignored for sub-prefix", num_ignored,
++ VTY_NEWLINE);
++ vty_out (vty, "%-30s: %10d%s", "Less specific routes to avoid", num_super,
++ VTY_NEWLINE);
++ /*
++ vty_out (vty, "There are %d routes in the RIB.%s", routes, VTY_NEWLINE);
++ vty_out (vty, "%d are anomalous.%s", anomalous, VTY_NEWLINE);
++ vty_out (vty, "%d anomalous routes are selected.%s", num_selected, VTY_NEWLINE);
++ vty_out (vty, "%s", VTY_NEWLINE);
++ vty_out (vty, "Anomaly breakdown:%s", VTY_NEWLINE);
++ vty_out (vty, "%d contain anomalous origins%s", num_origin, VTY_NEWLINE);
++ vty_out (vty, "%d contain anomalous edges.%s", num_edge, VTY_NEWLINE);
++ vty_out (vty, "%d are for ignored sub-prefixes.%s", num_ignored, VTY_NEWLINE);
++ vty_out (vty, "%d are super-net routes through peers that announced anomalous sub-prefixes.%s", num_super, VTY_NEWLINE);
++ */
++ return CMD_SUCCESS;
++}
++
++
++DEFUN (show_ip_bgp_pgbgp,
++ show_ip_bgp_pgbgp_cmd,
++ "show ip bgp pgbgp",
++ SHOW_STR IP_STR BGP_STR "Display PGBGP statistics\n")
++{
++ return bgp_pgbgp_stats (vty, AFI_IP, SAFI_UNICAST);
++}
++
++DEFUN (show_ip_bgp_pgbgp_neighbors,
++ show_ip_bgp_pgbgp_neighbors_cmd,
++ "show ip bgp pgbgp neighbors WORD",
++ SHOW_STR
++ IP_STR
++ BGP_STR
++ "BGP pgbgp\n"
++ "BGP pgbgp neighbors\n" "ASN whos neighbors should be displayed\n")
++{
++ return bgp_pgbgp_stats_neighbors (vty, AFI_IP, SAFI_UNICAST,
++ atoi (argv[0]));
++}
++
++DEFUN (show_ip_bgp_pgbgp_origins,
++ show_ip_bgp_pgbgp_origins_cmd,
++ "show ip bgp pgbgp origins A.B.C.D/M",
++ SHOW_STR
++ IP_STR
++ BGP_STR
++ "BGP pgbgp\n"
++ "BGP pgbgp neighbors\n" "Prefix to look up origin ASes of\n")
++{
++ return bgp_pgbgp_stats_origins (vty, AFI_IP, SAFI_UNICAST, argv[0]);
++}
++
++
++
++
++/*! --------------- VTY (others exist in bgp_route.c) ------------------ !*/
++
++
++
++
++
++
++
++/* --------------- Helper Functions ------------------ */
++/*
++ If the origin hasn't been seen/verified lately, look for it in the RIB
++*/
++int
++originInRIB (struct bgp_node *node, struct bgp_pgbgp_origin *origin)
++{
++ for (struct bgp_info * ri = node->info; ri; ri = ri->next)
++ {
++ struct bgp_pgbgp_pathSet pathOrigins;
++ pathOrigins = bgp_pgbgp_pathOrigin (ri->attr->aspath);
++ for (int i = 0; i < pathOrigins.length; ++i)
++ {
++ if (pathOrigins.ases[i] == origin->originAS)
++ {
++ return true;
++ }
++ }
++ }
++ return false;
++}
++
++
++/*
++ If the prefix hasn't been seen/verified lately, look for it in the RIB
++*/
++int
++prefixInRIB (struct bgp_node *node, struct bgp_pgbgp_prefix *prefix)
++{
++ if (node->info)
++ return true;
++ return false;
++}
++
++static int
++edge_inRIB_iterator (struct hash_backet *backet, struct bgp_pgbgp_edge *hedge)
++{
++ struct aspath *p = backet->data;
++ char first = true;
++ struct edge curEdge;
++ curEdge.a = 0;
++ curEdge.b = 0;
++
++ struct assegment *seg;
++
++ for (seg = p->segments; seg; seg = seg->next)
++ {
++ for (int i = 0; i < seg->length; i++)
++ {
++ curEdge.a = curEdge.b;
++ curEdge.b = seg->as[i];
++ if (first)
++ {
++ first = false;
++ continue;
++ }
++ // Is this the edge we're looking for?
++ if (curEdge.a == hedge->e.a && curEdge.b == hedge->e.b)
++ {
++ hedge->lastSeen = time (NULL);
++ return false;
++ }
++ }
++ }
++
++ return true;
++}
++
++/*
++ If the edge hasn't been seen/verified lately, look for it in the AS path list
++ This function is expensive, use sparingly
++*/
++int
++edgeInRIB (struct bgp_pgbgp_edge *e)
++{
++ int completed;
++ completed = hash_iterate_until (ashash,
++ (int (*)(struct hash_backet *, void *))
++ edge_inRIB_iterator, e);
++ if (completed)
++ return false;
++
++ return true;
++}
++
++
++
++/*
++ Return the selected route for the given route node
++ */
++
++struct bgp_info *
++bgp_pgbgp_selected (struct bgp_node *node)
++{
++ for (struct bgp_info * ri = node->info; ri; ri = ri->next)
++ {
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SELECTED))
++ return ri;
++ }
++ return NULL;
++}
++
++static int
++reuse_cmp (void *node1, void *node2)
++{
++ struct bgp_pgbgp_reuse *a;
++ struct bgp_pgbgp_reuse *b;
++ a = (struct bgp_pgbgp_reuse *) node1;
++ b = (struct bgp_pgbgp_reuse *) node2;
++ return a->deprefUntil - b->deprefUntil;
++}
++
++int
++bgp_pgbgp_pathLength (struct aspath *asp)
++{
++ struct assegment *seg;
++ if ((asp == NULL) || (asp->segments == NULL))
++ return 0;
++ int count = 0;
++ seg = asp->segments;
++ while (seg->next != NULL)
++ {
++ count += seg->length;
++ seg = seg->next;
++ }
++ return count;
++}
++
++
++
++/* Find the origin(s) of the path
++ All ASes in the final set are considered origins */
++static struct bgp_pgbgp_pathSet
++bgp_pgbgp_pathOrigin (struct aspath *asp)
++{
++ struct assegment *seg, *last;
++ struct bgp_pgbgp_pathSet tmp;
++ tmp.length = 0;
++ tmp.ases = NULL;
++
++ assert (asp != NULL && asp->segments != NULL);
++
++ /* if ( (asp == NULL) || (asp->segments == NULL) )
++ return tmp;
++ */
++ seg = asp->segments;
++ last = NULL;
++ while (seg->next != NULL)
++ {
++ if (seg->type != AS_SET && seg->type != AS_CONFED_SET)
++ last = seg;
++ seg = seg->next;
++ }
++
++ if (seg->type == AS_SET || seg->type == AS_CONFED_SET)
++ seg = last;
++
++ assert (seg);
++ tmp.length = 1;
++ tmp.ases = &seg->as[seg->length - 1];
++
++ /*
++ if (seg->type == AS_SET || seg->type == AS_CONFED_SET)
++ {
++ tmp.length = seg->length;
++ tmp.ases = seg->as;
++ }
++ else
++ {
++ tmp.length = 1;
++ tmp.ases = &seg->as[seg->length - 1];
++ }
++ */
++ assert (tmp.length >= 1);
++ return tmp;
++ // return seg->as[seg->length-1];
++}
++
++int
++bgp_pgbgp_reuse (time_t t_now)
++{
++
++ struct bgp_pgbgp_reuse *cur = NULL;
++
++ while (pgbgp->rq_size > 0)
++ {
++ cur = pqueue_dequeue (pgbgp->reuse_q);
++ pgbgp->rq_size -= 1;
++
++ // Is the next item ready to be reused?
++ if (t_now < cur->deprefUntil)
++ {
++ pqueue_enqueue (cur, pgbgp->reuse_q);
++ pgbgp->rq_size += 1;
++ break;
++ }
++
++ // Okay, it needs to be reused now
++ if (cur->type == PGBGP_REUSE_ORIGIN)
++ bgp_pgbgp_reuseOrigin (cur->data.origin);
++
++ else if (cur->type == PGBGP_REUSE_PREFIX)
++ bgp_pgbgp_reusePrefix (cur->data.prefix);
++
++ else if (cur->type == PGBGP_REUSE_EDGE)
++ bgp_pgbgp_reuseEdge (cur->data.edge);
++
++
++ XFREE (MTYPE_BGP_PGBGP_REUSE, cur);
++ }
++ return 0;
++}
++
++/* Check bit of the prefix. */
++static int
++check_bit (u_char * prefix, u_char prefixlen)
++{
++ int offset;
++ int shift;
++ u_char *p = (u_char *) prefix;
++
++ assert (prefixlen <= 128);
++
++ offset = prefixlen / 8;
++ shift = 7 - (prefixlen % 8);
++
++ return (p[offset] >> shift & 1);
++}
++
++/*
++ Find a super-net in the tree that's not currently anomalous if one exists
++*/
++struct bgp_node *
++findSuper (struct bgp_table *table, struct prefix *p, time_t t_now)
++{
++ struct bgp_node *node;
++ struct bgp_node *matched;
++
++ matched = NULL;
++ node = table->top;
++
++ while (node && node->p.prefixlen < p->prefixlen &&
++ prefix_match (&node->p, p))
++ {
++ // Node may not yet have its info set when reading in from pgbgp log files
++ if (node->hist && node->p.prefixlen >= 8)
++ {
++ if (node->hist->p != NULL && node->hist->p->ignoreUntil < t_now)
++ //if (node->hist->p != NULL && prefixInRIB (node, NULL))
++ //if (node->hist->p != NULL)
++ matched = node;
++ }
++ node = node->link[check_bit (&p->u.prefix, node->p.prefixlen)];
++ }
++ if (matched)
++ return bgp_lock_node (matched);
++ return NULL;
++}
++
++
++
++
++
++/*! --------------- Helper Functions ------------------ !*/
++
++
++
++
++
++
++
++/* --------------- Public PGBGP Interface ------------------ */
++int
++bgp_pgbgp_enable (struct bgp *bgp, afi_t afi, safi_t safi,
++ int ost, int est, int sst, int oht, int pht, int eht,
++ const char *file, const char *anoms)
++{
++
++ if (CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP))
++ {
++ if (pgbgp->storage && pgbgp->anomalies)
++ {
++ if (pgbgp->origin_sus_time == ost
++ && pgbgp->edge_sus_time == est
++ && pgbgp->sub_sus_time == sst
++ && pgbgp->origin_hist_time == oht
++ && pgbgp->prefix_hist_time == pht
++ && pgbgp->edge_hist_time == eht
++ && strcmp (pgbgp->storage, file) == 0
++ && strcmp (pgbgp->anomalies, anoms) == 0)
++
++ return 0;
++ }
++ }
++
++ SET_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP);
++
++#ifndef PGBGP_DEBUG
++ time_t hour = 3600;
++ time_t day = 86400;
++#endif
++#ifdef PGBGP_DEBUG
++ time_t hour = 2;
++ time_t day = 5;
++#endif
++
++ pgbgp->origin_sus_time = ost * hour;
++ pgbgp->edge_sus_time = est * hour;
++ pgbgp->sub_sus_time = sst * hour;
++ pgbgp->origin_hist_time = oht * day;
++ pgbgp->prefix_hist_time = pht * day;
++ pgbgp->edge_hist_time = eht * day;
++ pgbgp->peer_hist_time = DEFAULT_ORIGIN_HIST;
++
++ if (file != NULL)
++ pgbgp->storage = strdup (file);
++ else
++ pgbgp->storage = NULL;
++
++ if (anoms != NULL)
++ pgbgp->anomalies = strdup (anoms);
++ else
++ pgbgp->anomalies = NULL;
++
++
++ pgbgp->reuse_q = pqueue_create ();
++ pgbgp->reuse_q->cmp = reuse_cmp;
++ pgbgp->rq_size = 0;
++ pgbgp->lastgc = time (NULL);
++ pgbgp->lastStore = time (NULL);
++ pgbgp->startTime = time (NULL);
++ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_cmd);
++ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_neighbors_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_neighbors_cmd);
++ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_origins_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_origins_cmd);
++ pgbgp->edgeT = hash_create_size (131072, edge_key_make, edge_cmp);
++ bgp_pgbgp_restore ();
++ return 0;
++}
++
++int
++bgp_pgbgp_disable (struct bgp *bgp, afi_t afi, safi_t safi)
++{
++ UNSET_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP);
++
++ // Clean the tables
++ if (bgp->rib[afi][safi] != NULL)
++ bgp_pgbgp_clean (bgp->rib[afi][safi]);
++
++ bgp_pgbgp_cleanEdges ();
++
++ if (pgbgp->storage != NULL)
++ free (pgbgp->storage);
++
++ if (pgbgp->anomalies != NULL)
++ free (pgbgp->anomalies);
++
++ struct bgp_pgbgp_peerTime *pr = pgbgp->peerLast;
++ while (pr)
++ {
++ struct bgp_pgbgp_peerTime *cur = pr;
++ pr = pr->next;
++ XFREE (MTYPE_BGP_PGBGP_PEER, cur);
++ }
++
++ return 0;
++}
++
++int
++bgp_pgbgp_clean (struct bgp_table *table)
++{
++ struct bgp_pgbgp_reuse *rnode = NULL;
++
++ while (pgbgp->rq_size > 0)
++ {
++ rnode = (struct bgp_pgbgp_reuse *) pqueue_dequeue (pgbgp->reuse_q);
++ pgbgp->rq_size -= 1;
++ XFREE (MTYPE_BGP_PGBGP_REUSE, rnode);
++ }
++ pqueue_delete (pgbgp->reuse_q);
++
++ if (table == NULL)
++ return 0;
++
++ // Clean the detectors
++ bgp_pgbgp_cleanHistTable (table);
++
++ bgp_pgbgp_cleanEdges ();
++
++
++ // Clean up the RIB nodes
++ for (struct bgp_node * rn = bgp_table_top (table); rn;
++ rn = bgp_route_next (rn))
++ {
++ int changed = 0;
++ for (struct bgp_info * ri = rn->info; ri; ri = ri->next)
++ {
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_O
++ | BGP_INFO_SUSPICIOUS_P | BGP_INFO_SUSPICIOUS_E
++ | BGP_INFO_IGNORED_P))
++ {
++ changed = 1;
++ UNSET_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_O
++ | BGP_INFO_SUSPICIOUS_P | BGP_INFO_SUSPICIOUS_E
++ | BGP_INFO_IGNORED_P);
++ }
++ }
++ if (changed && rn->info)
++ {
++ struct bgp_info *ri = rn->info;
++ bgp_process (ri->peer->bgp, rn, rn->table->afi, rn->table->safi);
++ }
++ }
++
++ hash_free (pgbgp->edgeT);
++ return 0;
++}
++
++
++int
++bgp_pgbgp_gc (struct bgp_table *table)
++{
++ struct bgp *bgp = bgp_get_default ();
++ if (!bgp)
++ return 0;
++
++ // Collect each AFI/SAFI RIB
++ for (afi_t afi = AFI_IP; afi < AFI_MAX; afi++)
++ for (safi_t safi = SAFI_UNICAST; safi < SAFI_MAX; safi++)
++ {
++ if (!CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP))
++ continue;
++ struct bgp_table *curTable = bgp->rib[afi][safi];
++ if (!curTable)
++ continue;
++ bgp_pgbgp_garbageCollectHistTable (curTable);
++ }
++
++ bgp_pgbgp_garbageCollectEdges (table);
++
++ return 0;
++}
++
++int
++bgp_pgbgp_restore (void)
++{
++
++ if (pgbgp->storage == NULL)
++ return 0;
++ FILE *file = fopen (pgbgp->storage, "r");
++ if (!file)
++ return 0;
++
++ int type = 0;
++ struct prefix p;
++ struct bgp *bgp = bgp_get_default ();
++ struct bgp_node *curNode = NULL;
++
++ // Get the log store time
++ long long int writetime;
++ fscanf (file, "%lld", &writetime);
++ time_t swtime = writetime;
++
++ // If it's too old (more than 1 week old), start fresh
++ if (time (NULL) - swtime > 86400 * 7)
++ {
++ fclose (file);
++ return 0;
++ }
++
++
++ // Get the PGBGP init time
++ long long int stime;
++ fscanf (file, "%lld", &stime);
++ pgbgp->startTime = stime;
++
++ while (fscanf (file, "%d", &type) != EOF)
++ {
++
++ if (type == PREFIX_ID)
++ {
++ char pre[128];
++ unsigned int afi;
++ unsigned int safi;
++ long long int time;
++ fscanf (file, "%s %u %u %lld", pre, &afi, &safi, &time);
++ str2prefix (pre, &p);
++ struct bgp_table *curTable = bgp->rib[afi][safi];
++ assert (curTable != NULL);
++
++ // Create and lock the node
++ curNode = bgp_node_get (curTable, &p);
++ assert (curNode->hist == NULL);
++
++ // bgp_lock_node(curNode);
++
++ curNode->hist =
++ XCALLOC (MTYPE_BGP_PGBGP_HIST, sizeof (struct bgp_pgbgp_hist));
++ assert (curNode->hist != NULL);
++
++ curNode->hist->p =
++ XCALLOC (MTYPE_BGP_PGBGP_PREFIX,
++ sizeof (struct bgp_pgbgp_prefix));
++ assert (curNode->hist->p != NULL);
++
++ curNode->hist->p->lastSeen = time;
++ }
++ else if (type == ORIGIN_ID)
++ {
++ unsigned int ASN;
++ long long int time;
++ fscanf (file, "%u %lld", &ASN, &time);
++ struct bgp_pgbgp_origin *or = XCALLOC (MTYPE_BGP_PGBGP_ORIGIN,
++ sizeof (struct
++ bgp_pgbgp_origin));
++ or->lastSeen = time;
++ or->originAS = ASN;
++ or->next = curNode->hist->o;
++ curNode->hist->o = or;
++ }
++ else if (type == EDGE_ID)
++ {
++ bgp_pgbgp_restoreEdge (file);
++ }
++ else if (type == PEER_ID)
++ {
++ struct bgp_pgbgp_peerTime *pr;
++ long long int time;
++ union sockunion su;
++ char szsu[128];
++ fscanf (file, "%s %lld", szsu, &time);
++ str2sockunion (szsu, &su);
++ pr =
++ XCALLOC (MTYPE_BGP_PGBGP_PEER,
++ sizeof (struct bgp_pgbgp_peerTime));
++ pr->su = su;
++ pr->lastSeen = time;
++ pr->next = pgbgp->peerLast;
++ pgbgp->peerLast = pr;
++ }
++ }
++
++ fclose (file);
++ return 0;
++}
++
++int
++bgp_pgbgp_store (struct bgp_table *table)
++{
++ if (pgbgp->storage == NULL)
++ return 0;
++ char *tmpname = malloc (sizeof (char) * (1 + 4 + strlen (pgbgp->storage)));
++ strcpy (tmpname, pgbgp->storage);
++ strcat (tmpname, ".tmp");
++ FILE *file = fopen (tmpname, "w");
++
++ if (!file)
++ {
++ free (tmpname);
++ return 0;
++ }
++
++ // Store the current time
++ fprintf (file, "%lld\n", (long long int) time (NULL));
++
++ // Store the init time
++ fprintf (file, "%lld\n", (long long int) pgbgp->startTime);
++
++ // Store the peer times
++ for (struct bgp_pgbgp_peerTime * pr = pgbgp->peerLast; pr; pr = pr->next)
++ {
++ char strSock[128];
++ sockunion2str (&pr->su, strSock, sizeof (strSock));
++
++ if (pr->deprefUntil < time (NULL))
++ {
++ fprintf (file, "%d %s %lld\n", PEER_ID, strSock,
++ (long long int) pr->lastSeen);
++ }
++ }
++
++ // Store the tables
++ bgp_pgbgp_storeHistTable (table, file);
++ bgp_pgbgp_storeEdges (table, file);
++
++ fclose (file);
++
++ rename (tmpname, pgbgp->storage);
++
++ free (tmpname);
++ return 0;
++}
++
++/*
++ Check to see if we've seen the peer recently
++ If not, then we need to return true and not delay routes
++ for awhile
++*/
++int
++bgp_pgbgp_updatePeer (struct bgp_info *binfo, time_t now)
++{
++ int status = false;
++ // Find the peer
++ struct bgp_pgbgp_peerTime *pr = pgbgp->peerLast;
++ for (; pr; pr = pr->next)
++ if (sockunion_same (&pr->su, &binfo->peer->su))
++ break;
++
++ // If this is a new peer, create it
++ if (pr == NULL)
++ {
++ pr = XCALLOC (MTYPE_BGP_PGBGP_PEER, sizeof (struct bgp_pgbgp_peerTime));
++ pr->su = binfo->peer->su;
++ pr->next = pgbgp->peerLast;
++ pgbgp->peerLast = pr;
++
++ }
++ // Is it currently marked as new?
++ if (pr->deprefUntil > now)
++ goto UPPEER_DEPREF;
++
++ // Have we seen the peer recently?
++ if (pr->lastSeen + pgbgp->peer_hist_time > now)
++ goto UPPEER_CLEAN;
++
++ // It must not have been seen lately, depref it
++ pr->deprefUntil = now + PGBGP_PEER_GRACE;
++
++
++UPPEER_DEPREF:
++ status = true;
++
++UPPEER_CLEAN:
++ pr->lastSeen = now;
++
++ return status;
++}
++
++
++/*
++ Returns whether or not the sub-prefix should be ignored
++*/
++int
++bgp_pgbgp_shouldIgnore (struct bgp_node *super, struct bgp_info *selected)
++{
++ if (!selected || CHECK_FLAG (selected->flags, BGP_INFO_SUSPICIOUS_P))
++ return false;
++ return true;
++}
++
++/*
++ This is a special case function for smoothly handling sub-prefix hijacks.
++
++ It handles the following 2 events:
++
++ Event 1: The super-prefix of an anomalous prefix has a route through a non-anomalous
++
++ Event 1: An anomalous sub-prefix is ignored, but no best route for the super-prefix exists
++ Response: Announce the sub-prefix until the super-prefix comes back
++
++ Event 2: A super-prefix comes back to the RIB and its anomalous sub-prefix is in use
++ Response: Ignore the sub-prefix again
++ */
++
++
++int
++bgp_pgbgp_rib_updated (struct bgp_node *rn, struct bgp_info *old_best,
++ struct bgp_info *new_best)
++{
++ // return 0;
++ struct bgp_pgbgp_hist *hist = rn->hist;
++ if (!hist)
++ return 0;
++ if (!hist->p)
++ return 0;
++ time_t t_now = time (NULL);
++
++ /*
++ If we can't avoid the sub-prefix by routing to the super-prefix,
++ then route as normal to the sub-prefix
++ */
++ if (!bgp_pgbgp_shouldIgnore (rn, new_best))
++ {
++ for (struct bgp_pgbgp_avoid * cur = hist->p->avoid; cur;
++ cur = cur->next)
++ {
++ if (cur->avoidUntil > t_now)
++ {
++ int changed = false;
++ for (struct bgp_info * ri = cur->sub->info; ri; ri = ri->next)
++ {
++ if (CHECK_FLAG (ri->flags, BGP_INFO_IGNORED_P))
++ {
++ changed = true;
++ UNSET_FLAG (ri->flags, BGP_INFO_IGNORED_P);
++ }
++ }
++ if (changed)
++ {
++ struct bgp_info *ri = cur->sub->info;
++ if (ri && ri->peer && ri->peer->bgp)
++ bgp_process (ri->peer->bgp, cur->sub,
++ cur->sub->table->afi, cur->sub->table->safi);
++
++ }
++
++ }
++ }
++ }
++
++ /*
++ If we can avoid the sub-prefix by routing to the super-prefix,
++ then do so
++ */
++
++ else
++ {
++ for (struct bgp_pgbgp_avoid * cur = hist->p->avoid; cur;
++ cur = cur->next)
++ {
++ if (cur->avoidUntil > t_now)
++ {
++ int changed = false;
++ for (struct bgp_info * ri = cur->sub->info; ri; ri = ri->next)
++ {
++ if (!CHECK_FLAG (ri->flags, BGP_INFO_IGNORED_P))
++ {
++ changed = true;
++ SET_FLAG (ri->flags, BGP_INFO_IGNORED_P);
++ }
++ }
++ if (changed)
++ {
++ struct bgp_info *ri = cur->sub->info;
++ if (ri && ri->peer && ri->peer->bgp)
++ bgp_process (ri->peer->bgp, cur->sub,
++ cur->sub->table->afi, cur->sub->table->safi);
++ }
++ }
++ }
++ }
++
++ /*
++ if (old_best && !new_best)
++ {
++ time_t t_now = time(NULL);
++ for (struct bgp_pgbgp_avoid * cur = hist->p->avoid; cur;
++ cur = cur->next)
++ {
++ if (cur->avoidUntil > t_now)
++ {
++ for (struct bgp_info * ri = cur->sub->info; ri; ri = ri->next)
++ UNSET_FLAG (ri->flags, BGP_INFO_IGNORED_P);
++
++ struct bgp_info *ri = cur->sub->info;
++ if (ri && ri->peer && ri->peer->bgp)
++ bgp_process (ri->peer->bgp, cur->sub, cur->sub->table->afi,
++ cur->sub->table->safi);
++ }
++ }
++ }
++
++
++ else if (!old_best && new_best)
++ {
++ time_t t_now = time(NULL);
++ for (struct bgp_pgbgp_avoid * av = hist->p->avoid; av; av = av->next)
++ {
++ struct bgp_info * ri = av->sub->info;
++ if (av->avoidUntil > t_now && ri && !CHECK_FLAG(ri->flags, BGP_INFO_IGNORED_P))
++ {
++ for (; ri; ri = ri->next)
++ SET_FLAG (ri->flags, BGP_INFO_IGNORED_P);
++ ri = av->sub->info;
++ if (ri && ri->peer && ri->peer->bgp)
++ bgp_process (ri->peer->bgp, av->sub,
++ av->sub->table->afi, av->sub->table->safi);
++
++ }
++ }
++ }
++ */
++ return 0;
++}
++
++int
++bgp_pgbgp_update (struct bgp_info *binfo, struct attr *at,
++ struct bgp_node *rn)
++{
++ time_t t_now = time (NULL);
++
++ // Clean up the reuse list
++ bgp_pgbgp_reuse (t_now);
++
++
++ if (!rn->hist)
++ {
++ rn->hist =
++ XCALLOC (MTYPE_BGP_PGBGP_HIST, sizeof (struct bgp_pgbgp_hist));
++ // Get the PGBGP history lock on rn
++ bgp_lock_node (rn);
++ }
++
++ struct bgp_node *superhn = NULL;
++
++ // implicit lock from node_get
++ superhn = findSuper (rn->table, &rn->p, t_now);
++
++ int newPeer = bgp_pgbgp_updatePeer (binfo, t_now);
++ bgp_pgbgp_updateOrigin (rn->hist, binfo, at, rn, t_now, newPeer);
++ bgp_pgbgp_updatePrefix (rn->hist, superhn, binfo, at, rn, t_now, newPeer);
++ bgp_pgbgp_updateEdge (rn->hist, binfo, at, rn, t_now, newPeer);
++
++ if (superhn != NULL)
++ bgp_unlock_node (superhn);
++
++
++
++ // GC and storage must be last, as they update lastSeen values of objects
++ // which would cause new routes to be recently seen, which is undesired behavior
++ // Make sure you don't collect anything that might be in use!
++ if (t_now >= pgbgp->lastgc + PGBGP_GC_DELTA)
++ {
++ bgp_pgbgp_gc (rn->table);
++ pgbgp->lastgc = t_now;
++ }
++
++ if (t_now >= pgbgp->lastStore + PGBGP_STORE_DELTA)
++ {
++ bgp_pgbgp_store (rn->table);
++ pgbgp->lastStore = t_now;
++ }
++
++
++
++ return 0;
++}
++
++
++
++
++/*! --------------- Public PGBGP Interface ------------------ !*/
++
++
++
++
++
++
++
++
++
++/* --------------- MOAS Detection ------------------ */
++void
++bgp_pgbgp_storeHistTable (struct bgp_table *table, FILE * file)
++{
++ time_t t_now;
++ t_now = time (NULL);
++
++ struct bgp *bgp = bgp_get_default ();
++ if (!bgp)
++ return;
++
++ // Store each AFI/SAFI RIB
++ for (afi_t afi = AFI_IP; afi < AFI_MAX; afi++)
++ for (safi_t safi = SAFI_UNICAST; safi < SAFI_MAX; safi++)
++ {
++ if (!CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP))
++ continue;
++ struct bgp_table *curTable = bgp->rib[afi][safi];
++ if (!curTable)
++ continue;
++
++ for (struct bgp_node * rn = bgp_table_top (curTable); rn;
++ rn = bgp_route_next (rn))
++ {
++ struct bgp_pgbgp_hist *hist = rn->hist;
++ if (hist == NULL)
++ continue;
++ char szPrefix[128];
++ prefix2str (&rn->p, szPrefix, sizeof (szPrefix));
++
++
++ struct bgp_pgbgp_prefix *pre = hist->p;
++ if (pre && pre->ignoreUntil <= t_now)
++ {
++ if (pre->lastSeen + pgbgp->prefix_hist_time > t_now)
++ fprintf (file, "%d %s %u %u %lld\n", PREFIX_ID, szPrefix,
++ (unsigned int) afi, (unsigned int) safi,
++ (long long int) pre->lastSeen);
++ else
++ continue;
++ }
++ /* Need a prefix in the file before the origins,
++ if no prefix.. skip origins */
++ else
++ continue;
++
++ for (struct bgp_pgbgp_origin * cur = hist->o; cur;
++ cur = cur->next)
++ {
++ if (cur->deprefUntil > t_now)
++ continue;
++
++ if (cur->lastSeen + pgbgp->origin_hist_time > t_now)
++ fprintf (file, "%d %u %lld\n", ORIGIN_ID, cur->originAS,
++ (long long int) cur->lastSeen);
++ }
++
++ }
++ }
++}
++
++
++int
++bgp_pgbgp_garbageCollectHistTable (struct bgp_table *table)
++{
++ time_t t_now;
++ t_now = time (NULL);
++
++
++ for (struct bgp_node * rn = bgp_table_top (table); rn;
++ rn = bgp_route_next (rn))
++ {
++ int collect = false;
++ struct bgp_pgbgp_hist *hist = rn->hist;
++ if (hist == NULL)
++ continue;
++
++ struct bgp_pgbgp_origin *cur = hist->o;
++ struct bgp_pgbgp_prefix *pre = hist->p;
++ struct bgp_pgbgp_origin *parent = NULL;
++
++ int used = false;
++ if (cur != NULL || pre != NULL)
++ used = true;
++
++ while (cur != NULL)
++ {
++ // Update the lastSeen time w/ originInRIB
++ if (originInRIB (rn, cur))
++ cur->lastSeen = t_now;
++
++ collect = false;
++
++ // Collect if old
++ if (cur->lastSeen + pgbgp->origin_hist_time <= t_now)
++ collect = true;
++
++ // Collect if anomaly just became okay but not seen since last collection
++ if (cur->deprefUntil != 0 && cur->deprefUntil < t_now)
++ {
++ if (cur->lastSeen < pgbgp->lastgc)
++ collect = true;
++ cur->deprefUntil = 0;
++ }
++
++ if (collect)
++ {
++ if (parent == NULL)
++ hist->o = cur->next;
++ else
++ parent->next = cur->next;
++
++ // Delete cur, parent doesn't change
++ struct bgp_pgbgp_origin *del = cur;
++ cur = cur->next;
++ XFREE (MTYPE_BGP_PGBGP_ORIGIN, del);
++ }
++ else
++ {
++ parent = cur;
++ cur = cur->next;
++ }
++ }
++
++ // Update the lastSeen time w/ prefixInRIB
++ if (pre && prefixInRIB (rn, pre))
++ pre->lastSeen = t_now;
++
++ collect = false;
++
++ // Collect if old
++ if (pre && pre->lastSeen + pgbgp->prefix_hist_time <= t_now)
++ collect = true;
++
++ // Collect if anomaly just became okay but not seen since last collection
++ if (pre && pre->ignoreUntil != 0 && pre->ignoreUntil < t_now)
++ {
++ if (pre->lastSeen < pgbgp->lastgc)
++ collect = true;
++ pre->ignoreUntil = 0;
++ }
++
++ if (collect)
++ {
++ for (struct bgp_pgbgp_avoid * av = pre->avoid; av;)
++ {
++ struct bgp_pgbgp_avoid *del = av;
++ av = av->next;
++ bgp_unlock_node (del->sub);
++ XFREE (MTYPE_BGP_PGBGP_AVOID, del);
++ }
++
++ XFREE (MTYPE_BGP_PGBGP_PREFIX, pre);
++ hist->p = NULL;
++ }
++
++ // If the node isn't in use, remove it
++ if (used && hist->o == NULL && hist->p == NULL)
++ {
++ XFREE (MTYPE_BGP_PGBGP_HIST, hist);
++ rn->hist = NULL;
++ bgp_unlock_node (rn);
++ }
++ }
++
++ return 0;
++}
++
++void
++bgp_pgbgp_cleanHistTable (struct bgp_table *table)
++{
++ // Clean up the RIB nodes
++ for (struct bgp_node * rn = bgp_table_top (table); rn;
++ rn = bgp_route_next (rn))
++ {
++ struct bgp_pgbgp_hist *hist = rn->hist;
++ if (hist == NULL)
++ continue;
++
++ if (hist->p)
++ {
++ for (struct bgp_pgbgp_avoid * av = hist->p->avoid; av;)
++ {
++ struct bgp_pgbgp_avoid *del = av;
++ av = av->next;
++ bgp_unlock_node (del->sub);
++ XFREE (MTYPE_BGP_PGBGP_AVOID, del);
++ }
++ hist->p->avoid = NULL;
++ XFREE (MTYPE_BGP_PGBGP_PREFIX, hist->p);
++ hist->p = NULL;
++ }
++
++ for (struct bgp_pgbgp_origin * cur = hist->o; cur;)
++ {
++ struct bgp_pgbgp_origin *next = cur->next;
++ XFREE (MTYPE_BGP_PGBGP_ORIGIN, cur);
++ cur = next;
++ }
++ hist->o = NULL;
++ XFREE (MTYPE_BGP_PGBGP_HIST, hist);
++ rn->hist = NULL;
++ bgp_unlock_node (rn);
++ }
++}
++
++void
++bgp_pgbgp_logOriginAnomaly (as_t asn, struct bgp_node *rn, struct attr *at)
++{
++ assert (pgbgp);
++ if (!pgbgp->anomalies)
++ return;
++ FILE *file = fopen (pgbgp->anomalies, "a");
++ if (!file)
++ return;
++
++ char pre[256];
++ prefix2str (&rn->p, pre, sizeof (pre));
++
++ // MOAS | TIME | NEXTHOP | PREFIX | SUSPICIOUS_ORIGIN | TRUSTED_ORIGINS | PATH
++ fprintf (file, "%d|%lld|%s|%s|%d|", MOAS, (long long int) time (NULL),
++ inet_ntoa (at->nexthop), pre, asn);
++
++
++ // Print the trusted origins
++ assert (rn->hist);
++ assert (rn->hist->o);
++
++ struct bgp_pgbgp_hist *hist = rn->hist;
++
++ for (struct bgp_pgbgp_origin * cur = hist->o; cur != NULL; cur = cur->next)
++ {
++ if (cur->deprefUntil > time (NULL))
++ continue;
++ fprintf (file, "%d", cur->originAS);
++ if (cur->next != NULL)
++ fprintf (file, " ");
++ }
++
++ fprintf (file, " |%s\n", aspath_print (at->aspath));
++ fclose (file);
++}
++
++int
++bgp_pgbgp_updateOrigin (struct bgp_pgbgp_hist *hist, struct bgp_info *binfo,
++ struct attr *at, struct bgp_node *rn, time_t t_now,
++ int newPeer)
++{
++ struct bgp_pgbgp_pathSet pathOrigins;
++ struct bgp_pgbgp_origin *pi = NULL;
++ int status = 0;
++ struct bgp_pgbgp_reuse *r;
++ pathOrigins = bgp_pgbgp_pathOrigin (at->aspath);
++
++
++ for (int i = 0; i < pathOrigins.length; i++)
++ {
++ as_t pathOrigin = pathOrigins.ases[i];
++
++ /* Is the Origin AS in the history? */
++ for (pi = hist->o; pi; pi = pi->next)
++ if (pi->originAS == pathOrigin)
++ break;
++
++ if (pi == NULL)
++ {
++ pi =
++ XCALLOC (MTYPE_BGP_PGBGP_ORIGIN,
++ sizeof (struct bgp_pgbgp_origin));
++ pi->next = hist->o;
++ pi->originAS = pathOrigin;
++ hist->o = pi;
++ }
++
++ // If this is our first origin for the prefix, let the sub-prefix
++ // check take care of it
++ if (pi->next == NULL)
++ goto UPO_CLEAN;
++
++ /* Is the origin currently marked as suspicious? */
++ if (pi->deprefUntil > t_now)
++ goto UPO_DEPREF;
++
++ /* Have we seen the origin recently? */
++ if (pi->lastSeen + pgbgp->origin_hist_time > t_now)
++ goto UPO_CLEAN;
++
++#ifndef PGBGP_DEBUG
++ /* Are we within the initial grace period? */
++ if (newPeer)
++ goto UPO_CLEAN;
++#endif
++
++ /* It must not be in recent history, depref origin for first time */
++ pi->deprefUntil = t_now + pgbgp->origin_sus_time;
++ bgp_pgbgp_logOriginAnomaly (pathOrigin, rn, at);
++
++ r = XCALLOC (MTYPE_BGP_PGBGP_REUSE, sizeof (struct bgp_pgbgp_reuse));
++ r->type = PGBGP_REUSE_ORIGIN;
++ r->deprefUntil = pi->deprefUntil;
++ r->data.origin.originAS = pathOrigin;
++ r->data.origin.rn = rn;
++ bgp_lock_node (rn);
++ pqueue_enqueue (r, pgbgp->reuse_q);
++ pgbgp->rq_size += 1;
++
++
++ UPO_DEPREF:
++ SET_FLAG (binfo->flags, BGP_INFO_SUSPICIOUS_O);
++ status = BGP_INFO_SUSPICIOUS_O;
++
++ UPO_CLEAN:
++ pi->lastSeen = t_now;
++ }
++ return status;
++}
++
++int
++bgp_pgbgp_reuseOrigin (struct bgp_pgbgp_r_origin data)
++{
++ struct bgp_info *ri;
++ int numChanged = 0;
++ time_t t_now = time (NULL);
++ assert (data.rn->hist != NULL);
++
++ // Repreference paths for this prefix that are now okay
++ for (ri = data.rn->info; ri; ri = ri->next)
++ {
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_O))
++ {
++ struct bgp_pgbgp_pathSet pathOrigins;
++ pathOrigins = bgp_pgbgp_pathOrigin (ri->attr->aspath);
++ int numOkay = 0;
++ for (int i = 0; i < pathOrigins.length; i++)
++ {
++ as_t pathOrigin = pathOrigins.ases[i];
++ // Find the origin
++ struct bgp_pgbgp_origin *o = NULL;
++ for (o = data.rn->hist->o; o != NULL; o = o->next)
++ if (o->originAS == pathOrigin)
++ break;
++ /*
++ if (o == NULL) {
++ for(struct bgp_pgbgp_origin * z = data.rn->hist->o; z != NULL; z = z->next)
++ printf("Known origin: %d\n", z->originAS);
++ char pre[128];
++ prefix2str(&data.rn->p, pre, 128);
++ printf("%s : %s : %d\n", pre, ri->attr->aspath->str, pathOrigin);
++ }
++ */
++ assert (o != NULL);
++
++ if (o->deprefUntil <= t_now)
++ numOkay += 1;
++ }
++ if (numOkay == pathOrigins.length)
++ {
++ UNSET_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_O);
++ numChanged += 1;
++ }
++ }
++ }
++
++ ri = data.rn->info;
++
++ // Rerun the decision process?
++ if (numChanged > 0)
++ bgp_process (ri->peer->bgp, data.rn, data.rn->table->afi,
++ data.rn->table->safi);
++
++
++ /*
++ // Remove this (origin,prefix) pair from the normal database
++ // if it's not still in the RIB
++ struct bgp_pgbgp_hist *hist = rn->hist;
++ struct bgp_pgbgp_origin * cur = hist->o;
++ struct bgp_pgbgp_origin * parent = NULL;
++
++ // Find the origin AS node
++ while(cur != NULL)
++ {
++ if (cur->originAS == data.originAS)
++ {
++ // Delete the node if it hasn't been seen
++ // since the last storage run
++ if (cur->lastSeen < pgbgp->lastStore) {
++ // Delete this node
++ if (parent == NULL)
++ hist->o = cur->next;
++ else
++ parent->next = cur->next;
++
++ XFREE(MTYPE_BGP_PGBGP_ORIGIN, cur);
++ }
++ break;
++ }
++ parent = cur;
++ cur = cur->next;
++ }
++ */
++
++ bgp_unlock_node (data.rn);
++ return 0;
++}
++
++/*! --------------- MOAS Detection ------------------ !*/
++
++
++/* --------------- Sub-Prefix Detection ------------------ */
++
++
++
++
++
++void
++bgp_pgbgp_logSubprefixAnomaly (as_t asn, struct bgp_node *rn, struct attr *at,
++ struct bgp_node *super)
++{
++ assert (pgbgp);
++ if (!pgbgp->anomalies)
++ return;
++ FILE *file = fopen (pgbgp->anomalies, "a");
++ if (!file)
++ return;
++
++ char pre[256];
++ prefix2str (&rn->p, pre, sizeof (pre));
++
++ char superpre[256];
++ prefix2str (&super->p, superpre, sizeof (superpre));
++
++ // SUBPREFIX | TIME | NEXTHOP | PREFIX | SUPER-PREFIX | SUSPICIOUS_ORIGIN | TRUSTED_ORIGINS | PATH
++ fprintf (file, "%d|%lld|%s|%s|%s|%d|", SUBPREFIX,
++ (long long int) time (NULL), inet_ntoa (at->nexthop), pre,
++ superpre, asn);
++
++ // Print the trusted origins
++ assert (super->hist);
++ assert (super->hist->o);
++
++ struct bgp_pgbgp_hist *hist = super->hist;
++
++ for (struct bgp_pgbgp_origin * cur = hist->o; cur != NULL; cur = cur->next)
++ {
++ if (cur->deprefUntil > time (NULL))
++ continue;
++ fprintf (file, "%d", cur->originAS);
++ if (cur->next != NULL)
++ fprintf (file, " ");
++ }
++
++ fprintf (file, " |%s\n", aspath_print (at->aspath));
++ fclose (file);
++}
++
++/*
++ If the first path is a prefix of the second, then return true
++ */
++
++static int
++bgp_pgbgp_pathIsPrefix(struct aspath *trusted, struct aspath * new)
++{
++ if (trusted == new)
++ return true;
++
++ struct assegment *seg1 = trusted->segments;
++ struct assegment *seg2 = new->segments;
++
++ while (seg1 || seg2)
++ {
++ if ((!seg1 && seg2) || (seg1 && !seg2))
++ return false;
++ if (seg1->type != seg2->type)
++ return false;
++
++ if (seg1->length > seg2->length)
++ return false;
++
++ for(int i = 0; i < seg1->length; i++)
++ if (seg1->as[i] != seg2->as[i])
++ return false;
++
++ seg1 = seg1->next;
++ seg2 = seg2->next;
++ }
++
++ return true;
++}
++
++int
++bgp_pgbgp_updatePrefix (struct bgp_pgbgp_hist *hist,
++ struct bgp_node *supernode, struct bgp_info *binfo,
++ struct attr *at, struct bgp_node *rn, time_t t_now,
++ int newPeer)
++{
++ struct bgp_pgbgp_prefix *pre = NULL;
++ struct bgp_pgbgp_reuse *r = NULL;
++ int status = 0;
++ int changed = false;
++
++ pre = hist->p;
++
++
++ /* Do we have this prefix? */
++ if (pre == NULL)
++ {
++ pre =
++ XCALLOC (MTYPE_BGP_PGBGP_PREFIX, sizeof (struct bgp_pgbgp_prefix));
++ hist->p = pre;
++ }
++
++ /* Is the prefix currently marked as suspicious? */
++ if (pre->ignoreUntil > t_now)
++ {
++ goto UPP_IGNORE;
++ }
++
++ /* Should this neighbor be avoided for this prefix because it
++ sent us info. about a suspicious sub-prefix? */
++ for (struct bgp_pgbgp_avoid * av = hist->p->avoid; av; av = av->next)
++ {
++ if (binfo->peer->as == av->peerASN && av->avoidUntil > t_now)
++ {
++ SET_FLAG (binfo->flags, BGP_INFO_SUSPICIOUS_P);
++ status = BGP_INFO_SUSPICIOUS_P;
++ goto UPP_DONE;
++ }
++ }
++
++ /* Have we seen the prefix recently? */
++ if (pre->lastSeen + pgbgp->prefix_hist_time > t_now)
++ goto UPP_DONE;
++
++#ifndef PGBGP_DEBUG
++ /* Are we within the initial grace period? */
++ if (newPeer)
++ goto UPP_DONE;
++#endif
++
++ /* Is there a less specific *in recent history* that this could be hijacking? */
++ if (supernode == NULL)
++ goto UPP_DONE;
++
++ /* Does this path the super-net's non-anomalous path from this peer? If so it's okay */
++ int found = false;
++ for (struct bgp_info * ri = supernode->info; ri; ri = ri->next)
++ {
++ if (ri->peer->as == binfo->peer->as)
++ {
++ if (!ANOMALOUS(ri->flags) && bgp_pgbgp_pathIsPrefix(ri->attr->aspath, at->aspath))
++ found = true;
++ break;
++ }
++ }
++
++ if (found)
++ goto UPP_DONE;
++
++ /*
++ It's not in recent history, and there is a less specific currently in use
++ Response:
++ . Ignore this prefix
++ . Make the less specific's route for this neighbor suspicious
++ */
++
++
++ pre->ignoreUntil = t_now + pgbgp->sub_sus_time;
++
++ struct bgp_pgbgp_pathSet pathOrigins;
++ pathOrigins = bgp_pgbgp_pathOrigin (at->aspath);
++ for (int i = 0; i < pathOrigins.length; i++)
++ bgp_pgbgp_logSubprefixAnomaly (pathOrigins.ases[i], rn, at, supernode);
++
++
++
++ r = XCALLOC (MTYPE_BGP_PGBGP_REUSE, sizeof (struct bgp_pgbgp_reuse));
++ r->type = PGBGP_REUSE_PREFIX;
++ r->deprefUntil = pre->ignoreUntil;
++ r->data.prefix.rn = rn;
++ r->data.prefix.rnsuper = supernode;
++ bgp_lock_node (rn);
++ bgp_lock_node (supernode);
++ pqueue_enqueue (r, pgbgp->reuse_q);
++ pgbgp->rq_size += 1;
++
++UPP_IGNORE:
++ // Sanity check
++ if (supernode == NULL)
++ goto UPP_DONE;
++
++ /* Set the less specific's route from this peer to suspicious */
++ changed = false;
++
++ for (struct bgp_info * ri = supernode->info; ri; ri = ri->next)
++ {
++ if (ri->peer->as == binfo->peer->as)
++ {
++ if (!CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_P))
++ {
++ SET_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_P);
++ changed = true;
++ }
++ break;
++ }
++ }
++
++ // Make note of it in the less specific's history information
++ found = false;
++ struct bgp_pgbgp_hist *superhist = supernode->hist;
++
++ if (superhist && superhist->p)
++ {
++ for (struct bgp_pgbgp_avoid * av = superhist->p->avoid; av;
++ av = av->next)
++ {
++ if (av->peerASN == binfo->peer->as)
++ {
++ if (av->avoidUntil < pre->ignoreUntil)
++ av->avoidUntil = pre->ignoreUntil;
++ found = true;
++ break;
++ }
++ }
++ if (!found)
++ {
++ struct bgp_pgbgp_avoid *newavoid =
++ XCALLOC (MTYPE_BGP_PGBGP_AVOID, sizeof (struct bgp_pgbgp_avoid));
++ newavoid->peerASN = binfo->peer->as;
++ newavoid->avoidUntil = pre->ignoreUntil;
++ newavoid->next = superhist->p->avoid;
++ newavoid->sub = rn;
++ bgp_lock_node (rn);
++ superhist->p->avoid = newavoid;
++ }
++ }
++ /*
++ ignore this route unless the supernet's node
++ is only a placeholder from loaded pgbgp data
++ */
++ if (bgp_pgbgp_shouldIgnore (supernode, bgp_pgbgp_selected (supernode)))
++ {
++ SET_FLAG (binfo->flags, BGP_INFO_IGNORED_P);
++ status = BGP_INFO_IGNORED_P;
++ }
++ if (changed)
++ {
++ struct bgp_info *ri = supernode->info;
++ bgp_process (ri->peer->bgp, supernode, supernode->table->afi,
++ supernode->table->safi);
++ }
++
++UPP_DONE:
++ pre->lastSeen = t_now;
++
++ return status;
++}
++
++int
++bgp_pgbgp_reusePrefix (struct bgp_pgbgp_r_prefix data)
++{
++ struct bgp_info *ri = NULL;
++
++ time_t t_now = time (NULL);
++
++ // Repreference all routes for this node
++ for (ri = data.rn->info; ri; ri = ri->next)
++ UNSET_FLAG (ri->flags, BGP_INFO_IGNORED_P);
++ ri = data.rn->info;
++
++ // Rerun the decision process
++ if (ri != NULL)
++ bgp_process (ri->peer->bgp, data.rn, data.rn->table->afi,
++ data.rn->table->safi);
++
++
++ // Remove the avoid nodes from the super
++ struct bgp_pgbgp_hist *superhist = data.rnsuper->hist;
++ if (superhist != NULL && superhist->p != NULL)
++ {
++ struct bgp_pgbgp_avoid *parent = NULL;
++ for (struct bgp_pgbgp_avoid * av = superhist->p->avoid; av;)
++ {
++ int numChanged = 0;
++ if (av->avoidUntil <= t_now)
++ {
++ struct bgp_pgbgp_avoid *del = av;
++ av = av->next;
++ if (parent == NULL)
++ superhist->p->avoid = av;
++ else
++ parent->next = av;
++
++ // Repreference any routes
++ for (ri = data.rnsuper->info; ri; ri = ri->next)
++ {
++ if (ri->peer->as == del->peerASN)
++ {
++ UNSET_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_P);
++ numChanged += 1;
++ break;
++ }
++ }
++ ri = data.rnsuper->info;
++
++ if (numChanged > 0 && ri != NULL)
++ bgp_process (ri->peer->bgp, data.rnsuper,
++ data.rnsuper->table->afi,
++ data.rnsuper->table->safi);
++ bgp_unlock_node (del->sub);
++ XFREE (MTYPE_BGP_PGBGP_AVOID, del);
++ }
++ else
++ {
++ parent = av;
++ av = av->next;
++ }
++ }
++ }
++
++ // Remove this prefix from the normal database
++ // if it hasn't been seen in the RIB since the last
++ // storage run
++ /*
++ struct bgp_pgbgp_hist *hist = rn->hist;
++ struct bgp_pgbgp_prefix * pre = hist->p;
++
++ if (pre && pre->lastSeen < pgbgp->lastStore)
++ {
++ // Delete this node
++ for(struct bgp_pgbgp_avoid * av = hist->p->avoid; av;)
++ {
++ struct bgp_pgbgp_avoid *del = av;
++ av = av->next;
++ bgp_unlock_node(del->sub);
++ XFREE (MTYPE_BGP_PGBGP_AVOID, del);
++ }
++ XFREE(MTYPE_BGP_PGBGP_PREFIX, pre);
++ hist->p = NULL;
++ }
++ */
++ bgp_unlock_node (data.rn);
++ bgp_unlock_node (data.rnsuper);
++ return 0;
++}
++
++/*! --------------- Sub-Prefix Detection ------------------ !*/
++
++
++
++
++
++/* --------------- Edge Detection ------------------ */
++
++static void
++edge_store_clear_iterator (struct hash_backet *backet, void *file)
++{
++ struct bgp_pgbgp_edge *hedge = backet->data;
++}
++
++static void
++edge_store_iterator (struct hash_backet *backet, FILE * file)
++{
++ struct bgp_pgbgp_edge *hedge = backet->data;
++ time_t t_now = time (NULL);
++ if (hedge->deprefUntil > t_now)
++ return;
++ if (hedge->lastSeen + pgbgp->edge_hist_time > t_now)
++ {
++ fprintf (file, "%d %u %u %lld\n", EDGE_ID, hedge->e.a, hedge->e.b,
++ (long long int) hedge->lastSeen);
++ }
++}
++
++
++void
++bgp_pgbgp_storeEdges (struct bgp_table *table, FILE * file)
++{
++ hash_iterate (pgbgp->edgeT,
++ (void (*)(struct hash_backet *, void *))
++ edge_store_iterator, file);
++ return;
++}
++
++
++int
++bgp_pgbgp_restoreEdge (FILE * file)
++{
++ unsigned int a, b;
++ long long int lastSeen;
++ fscanf (file, "%u %u %lld", &a, &b, &lastSeen);
++ struct bgp_pgbgp_edge finder;
++ finder.e.a = a;
++ finder.e.b = b;
++ finder.lastSeen = lastSeen;
++ struct bgp_pgbgp_edge *hedge =
++ hash_get (pgbgp->edgeT, &finder, edge_hash_alloc);
++ hedge->lastSeen = finder.lastSeen;
++ return 0;
++}
++
++unsigned int
++edge_key_make (void *p)
++{
++ struct bgp_pgbgp_edge *pe = p;
++ struct edge *e = &pe->e;
++ return (e->a << 16) + e->b;
++}
++
++static int
++edge_cmp (const void *arg1, const void *arg2)
++{
++
++ const struct edge *e1 = &((const struct bgp_pgbgp_edge *) arg1)->e;
++ const struct edge *e2 = &((const struct bgp_pgbgp_edge *) arg2)->e;
++ if (e1->a == e2->a && e1->b == e2->b)
++ return 1;
++ return 0;
++}
++
++static void *
++edge_hash_alloc (void *arg)
++{
++ struct bgp_pgbgp_edge *hedge =
++ XCALLOC (MTYPE_BGP_PGBGP_EDGE, sizeof (struct bgp_pgbgp_edge));
++ struct bgp_pgbgp_edge *lookup = arg;
++ if (hedge == NULL)
++ return NULL;
++ hedge->e = lookup->e;
++ return hedge;
++}
++
++
++static void
++edge_gc_iterator (struct hash_backet *backet, time_t * time)
++{
++ time_t t_now = *time;
++ struct bgp_pgbgp_edge *hedge = backet->data;
++
++ int collect = false;
++
++ // Collect if we haven't seen it in awhile
++ if (hedge->lastSeen + pgbgp->edge_hist_time <= t_now)
++ collect = true;
++
++ // Collect if it has just gotten out of anomaly stage
++ // but hasn't been in the RIB since the last GC
++ if (hedge->deprefUntil != 0 && hedge->deprefUntil < t_now)
++ {
++ if (hedge->lastSeen < pgbgp->lastgc)
++ collect = true;
++ hedge->deprefUntil = 0;
++ }
++
++ if (collect)
++ {
++ struct bgp_pgbgp_edge *ret = hash_release (pgbgp->edgeT, hedge);
++ assert (ret != NULL);
++ XFREE (MTYPE_BGP_PGBGP_EDGE, hedge);
++ }
++}
++
++
++
++static void
++edge_update_iterator (struct hash_backet *backet, void *v)
++{
++ struct aspath *p = backet->data;
++ time_t t_now = time (NULL);
++ int first = true;
++
++ struct edge cur;
++ cur.a = 0;
++ cur.b = 0;
++ struct assegment *seg;
++ struct bgp_pgbgp_edge *hedge = NULL;
++ for (seg = p->segments; seg; seg = seg->next)
++ {
++ for (int i = 0; i < seg->length; i++)
++ {
++ cur.a = cur.b;
++ cur.b = seg->as[i];
++ if (first)
++ {
++ first = false;
++ continue;
++ }
++ if (cur.a == cur.b)
++ continue;
++ // printf("%d -- %d\n", cur.a, cur.b);
++ struct bgp_pgbgp_edge finder;
++ finder.e = cur;
++ hedge = hash_lookup (pgbgp->edgeT, &finder);
++
++ if (!hedge)
++ continue;
++ hedge->lastSeen = t_now;
++ }
++ }
++}
++
++int
++bgp_pgbgp_garbageCollectEdges (struct bgp_table *table)
++{
++ // Update the timings
++ hash_iterate (ashash,
++ (void (*)(struct hash_backet *, void *))
++ edge_update_iterator, NULL);
++
++ // Perform the collection
++ time_t t_now = time (NULL);
++ hash_iterate (pgbgp->edgeT,
++ (void (*)(struct hash_backet *, void *))
++ edge_gc_iterator, &t_now);
++ return 0;
++}
++
++static void
++edge_clean_iterator (struct hash_backet *backet, void *a1)
++{
++ struct bgp_pgbgp_edge *hedge = backet->data;
++ struct bgp_pgbgp_edge *ret = hash_release (pgbgp->edgeT, hedge);
++ assert (ret != NULL);
++ XFREE (MTYPE_BGP_PGBGP_EDGE, hedge);
++}
++
++static void
++bgp_pgbgp_cleanEdges (void)
++{
++ if (pgbgp->edgeT != NULL)
++ {
++ hash_iterate (pgbgp->edgeT,
++ (void (*)(struct hash_backet *, void *))
++ edge_clean_iterator, NULL);
++ hash_free (pgbgp->edgeT);
++ }
++ return;
++}
++
++void
++bgp_pgbgp_logEdgeAnomaly (struct bgp_node *rn, struct attr *at,
++ struct edge *edge)
++{
++ assert (pgbgp);
++ if (!pgbgp->anomalies)
++ return;
++ FILE *file = fopen (pgbgp->anomalies, "a");
++ if (!file)
++ return;
++
++ char pre[256];
++ prefix2str (&rn->p, pre, sizeof (pre));
++
++ // EDGE | TIME | NEXTHOP | PREFIX | PATH | Edge.a | Edge.b
++
++ fprintf (file, "%d|%lld|%s|%s|%s|%d|%d\n", EDGE,
++ (long long int) time (NULL), inet_ntoa (at->nexthop), pre,
++ aspath_print (at->aspath), edge->a, edge->b);
++
++ fclose (file);
++}
++
++
++int
++bgp_pgbgp_updateEdge (struct bgp_pgbgp_hist *hist, struct bgp_info *binfo,
++ struct attr *at, struct bgp_node *rn, time_t t_now,
++ int newPeer)
++{
++
++ char first = true;
++ struct edge curEdge;
++ curEdge.a = 0;
++ curEdge.b = 0;
++
++
++ if (at->aspath == NULL)
++ return 0;
++ struct assegment *seg = at->aspath->segments;
++ if (seg == NULL)
++ return 0;
++ time_t max_depref = 0;
++ for (seg = at->aspath->segments; seg; seg = seg->next)
++ {
++ for (int i = 0; i < seg->length; i++)
++ {
++ curEdge.a = curEdge.b;
++ curEdge.b = seg->as[i];
++ if (first)
++ {
++ first = false;
++ continue;
++ }
++ if (curEdge.a == curEdge.b)
++ continue;
++
++ // We have an edge to consider
++ struct bgp_pgbgp_edge finder;
++ finder.e = curEdge;
++ struct bgp_pgbgp_edge *hedge =
++ hash_get (pgbgp->edgeT, &finder, edge_hash_alloc);
++
++ // Is this edge marked as suspicious?
++ if (hedge->deprefUntil > t_now)
++ goto UPE_DEPREF;
++
++ // Have we seen the edge recently?
++ if (hedge->lastSeen + pgbgp->edge_hist_time > t_now)
++ goto UPE_CLEAN;
++#ifndef PGBGP_DEBUG
++ /* Are we within the initial grace period? */
++ if (newPeer)
++ goto UPE_CLEAN;
++#endif
++ // It must not be in recent history, depref edge for first time
++ hedge->deprefUntil = t_now + pgbgp->edge_sus_time;
++ bgp_pgbgp_logEdgeAnomaly (rn, at, &curEdge);
++
++
++ UPE_DEPREF:
++ if (hedge->deprefUntil > max_depref)
++ max_depref = hedge->deprefUntil;
++ UPE_CLEAN:
++ hedge->lastSeen = t_now;
++ }
++ }
++ if (max_depref)
++ {
++ SET_FLAG (binfo->flags, BGP_INFO_SUSPICIOUS_E);
++ if (!hist->pEdgeReuse)
++ {
++ struct bgp_pgbgp_reuse *r;
++ r =
++ XCALLOC (MTYPE_BGP_PGBGP_REUSE, sizeof (struct bgp_pgbgp_reuse));
++ r->type = PGBGP_REUSE_EDGE;
++ r->deprefUntil = max_depref;
++ r->data.edge.rn = rn;
++ bgp_lock_node (rn);
++ pqueue_enqueue (r, pgbgp->reuse_q);
++ pgbgp->rq_size += 1;
++ hist->pEdgeReuse = r;
++ }
++ return BGP_INFO_SUSPICIOUS_E;
++ }
++
++ return 0;
++}
++
++int
++bgp_pgbgp_reuseEdge (struct bgp_pgbgp_r_edge data)
++{
++
++ // Okay, go through all of the paths for the prefix
++ // and find the path that needs to be updated next and
++ // enqueue it
++ time_t minMax = 0;
++ int numChanged = 0;
++ time_t t_now = time (NULL);
++
++ for (struct bgp_info * ri = data.rn->info; ri; ri = ri->next)
++ {
++ char first = true;
++ struct edge curEdge = { 0, 0 };
++ struct assegment *seg;
++ time_t max_depref = 0;
++
++ for (seg = ri->attr->aspath->segments; seg; seg = seg->next)
++ {
++ for (int i = 0; i < seg->length; i++)
++ {
++ curEdge.a = curEdge.b;
++ curEdge.b = seg->as[i];
++ if (first)
++ {
++ first = false;
++ continue;
++ }
++ struct bgp_pgbgp_edge finder;
++ finder.e = curEdge;
++ struct bgp_pgbgp_edge *hedge =
++ hash_lookup (pgbgp->edgeT, &finder);
++ if (!hedge)
++ continue;
++ // Is this edge suspicious?
++ if (hedge->deprefUntil > t_now
++ && hedge->deprefUntil > max_depref)
++ max_depref = hedge->deprefUntil;
++ }
++ }
++
++ if (max_depref)
++ {
++ if (!minMax || max_depref < minMax)
++ minMax = max_depref;
++ }
++ else
++ {
++ if (CHECK_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_E))
++ {
++ UNSET_FLAG (ri->flags, BGP_INFO_SUSPICIOUS_E);
++ numChanged += 1;
++ }
++ }
++ }
++ struct bgp_info *ri = data.rn->info;
++ if (numChanged > 0 && ri)
++ bgp_process (ri->peer->bgp, data.rn, data.rn->table->afi,
++ data.rn->table->safi);
++
++ struct bgp_pgbgp_hist *hist = data.rn->hist;
++ hist->pEdgeReuse = NULL;
++
++ if (minMax)
++ {
++ struct bgp_pgbgp_reuse *r;
++ r = XCALLOC (MTYPE_BGP_PGBGP_REUSE, sizeof (struct bgp_pgbgp_reuse));
++ r->type = PGBGP_REUSE_EDGE;
++ r->deprefUntil = minMax;
++ r->data.edge.rn = data.rn;
++ pqueue_enqueue (r, pgbgp->reuse_q);
++ pgbgp->rq_size += 1;
++ hist->pEdgeReuse = r;
++ }
++ else
++ {
++ bgp_unlock_node (data.rn);
++ }
++
++ return 0;
++}
+--- /dev/null
++++ b/bgpd/bgp_pgbgp.h
+@@ -0,0 +1,286 @@
++/* BGP Pretty Good BGP
++ Copyright (C) 2008 University of New Mexico (Josh Karlin)
++
++This file is part of GNU Zebra.
++
++GNU Zebra is free software; you can redistribute it and/or modify it
++under the terms of the GNU General Public License as published by the
++Free Software Foundation; either version 2, or (at your option) any
++later version.
++
++GNU Zebra is distributed in the hope that it will be useful, but
++WITHOUT ANY WARRANTY; without even the implied warranty of
++MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
++General Public License for more details.
++
++You should have received a copy of the GNU General Public License
++along with GNU Zebra; see the file COPYING. If not, write to the Free
++Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
++02111-1307, USA. */
++
++#ifndef _QUAGGA_BGP_PGBGP_H
++#define _QUAGGA_BGP_PGBGP_H
++
++#include "bgpd.h"
++#include "bgp_route.h"
++#include "table.h"
++
++#define MOAS 0
++#define SUBPREFIX 1
++#define EDGE 2
++
++/* Global PGBGP data */
++struct bgp_pgbgp_config
++{
++ /* Depref time for a new origin AS */
++ time_t origin_sus_time;
++
++ /* Depref time for a new edge */
++ time_t edge_sus_time;
++
++ /* Depref time for a new sub-prefix */
++ time_t sub_sus_time;
++
++ /* Origin AS Mapping History Length */
++ time_t origin_hist_time;
++
++ /* Prefix Mapping History Length */
++ time_t prefix_hist_time;
++
++ /* Edge Mapping History Length */
++ time_t edge_hist_time;
++
++ /* Peer Mapping History Length */
++ time_t peer_hist_time;
++
++ /* The list of depreferenced routes */
++ struct pqueue *reuse_q;
++ int rq_size;
++
++ /* Time that the last garbage collection (gc) took place */
++ time_t lastgc;
++
++ /* History table */
++ // struct route_table *histT;
++
++ /* Edge Hash Table */
++ struct hash *edgeT;
++
++ /* File path for history storage */
++ char *storage;
++
++ /* File path for dump of anomalous routes */
++ char *anomalies;
++
++ /* The time that we last stored to disk */
++ time_t lastStore;
++
++ /* The time that PGBGP started counting */
++ time_t startTime;
++
++ /* Last time each peer was seen */
++ struct bgp_pgbgp_peerTime *peerLast;
++
++};
++
++
++struct bgp_pgbgp_peerTime
++{
++ struct bgp_pgbgp_peerTime *next;
++ time_t lastSeen;
++ union sockunion su;
++ time_t deprefUntil;
++};
++
++struct edge
++{
++ as_t a;
++ as_t b;
++};
++
++/*
++ Avoid the neighbors for the less specific that told you about
++ the more specific
++ */
++struct bgp_pgbgp_avoid
++{
++ struct bgp_pgbgp_avoid *next;
++ time_t avoidUntil;
++ as_t peerASN;
++ struct bgp_node *sub;
++};
++
++/* A list of origin ASes for a path
++ Usually it's only one but if the last AS
++ in the path is an AS set, then the whole
++ set must be returned
++*/
++struct bgp_pgbgp_pathSet
++{
++ int length;
++ as_t *ases;
++};
++
++/*
++ Avoid paths with suspicious origins
++ */
++struct bgp_pgbgp_origin
++{
++ struct bgp_pgbgp_origin *next;
++ time_t lastSeen;
++ time_t deprefUntil;
++ as_t originAS;
++};
++
++/*
++ Ignore routes for this prefix
++ */
++struct bgp_pgbgp_prefix
++{
++ time_t lastSeen;
++ time_t ignoreUntil;
++ struct bgp_pgbgp_avoid *avoid;
++};
++
++struct bgp_pgbgp_edge
++{
++ time_t lastSeen;
++ time_t deprefUntil;
++ struct edge e;
++};
++
++struct bgp_pgbgp_hist
++{
++ struct bgp_pgbgp_origin *o;
++ struct bgp_pgbgp_prefix *p;
++ struct bgp_pgbgp_reuse *pEdgeReuse;
++};
++
++struct bgp_pgbgp_r_origin
++{
++ as_t originAS;
++ struct bgp_node *rn;
++};
++
++struct bgp_pgbgp_r_prefix
++{
++ struct bgp_node *rn;
++ struct bgp_node *rnsuper;
++};
++
++/*
++ This node contained a route with a bad edge, check
++ it again for bad edges in 24 hours
++*/
++struct bgp_pgbgp_r_edge
++{
++ struct bgp_node *rn;
++};
++
++
++union reuseTypes
++{
++ struct bgp_pgbgp_r_origin origin;
++ struct bgp_pgbgp_r_prefix prefix;
++ struct bgp_pgbgp_r_edge edge;
++};
++
++struct bgp_pgbgp_reuse
++{
++ union reuseTypes data;
++ short type;
++ time_t deprefUntil;
++};
++
++#define ANOMALOUS(V) \
++(CHECK_FLAG(V, BGP_INFO_SUSPICIOUS_O | BGP_INFO_SUSPICIOUS_P \
++ | BGP_INFO_SUSPICIOUS_E | BGP_INFO_IGNORED_P))
++
++#define PGBGP_REUSE_ORIGIN 0
++#define PGBGP_REUSE_PREFIX 1
++#define PGBGP_REUSE_EDGE 2
++
++#define BGP_PGBGP_NONE 0
++#define BGP_PGBGP_DEPREFFED 1
++
++// For storage
++#define ORIGIN_ID 0
++#define PREFIX_ID 1
++#define EDGE_ID 2
++#define PEER_ID 3
++
++/* Default timing values */
++#define DEFAULT_ORIGIN_SUS (86400 * 1)
++#define DEFAULT_EDGE_SUS (86400 * 1)
++#define DEFAULT_SUB_SUS (86400 * 1)
++#define DEFAULT_ORIGIN_HIST (86400 * 30)
++#define DEFAULT_PREFIX_HIST (86400 * 10)
++#define DEFAULT_EDGE_HIST (86400 * 60)
++// Time between garbage collections
++#define PGBGP_GC_DELTA (3600)
++// Time between file stores
++#define PGBGP_STORE_DELTA (28800)
++// Time that a new peer's routes are not considered suspicious
++#define PGBGP_PEER_GRACE (86400 * 1)
++
++
++
++///////// PUBLIC PGBGP FUNCTIONS /////////
++
++/*
++ bgp_pgbgp_enable:
++ Enable PGBGP depreferencing / history tracking for this afi/safi
++
++ Arguments:
++ . ost: Depref. time of new prefix origins (in hours)
++ . est: Depref. time of new edges (in hours)
++ . sst: Depref. time of new sub-prefixes (in hours)
++ . oht: Storage time of known origins for prefixes (in days)
++ . pht: Storage time of known prefixes (in days)
++ . eht: Storage time of known edges (in days)
++ . storage: File to periodically store history in (can be /dev/null)
++ . anoms: File to store history of depreferenced routes (can be /dev/null)
++
++ Caution:
++ It is important that the storage times are longer than the depreference times
++*/
++extern int bgp_pgbgp_enable (struct bgp *, afi_t afi, safi_t safi, int ost,
++ int est, int sst, int oht, int pht, int eht,
++ const char *storage, const char *anoms);
++extern int bgp_pgbgp_disable (struct bgp *, afi_t afi, safi_t safi);
++
++/*
++ bgp_pgbgp_update:
++ Call on the event of an announcement update
++
++ Arguments:
++ bgp_info: The route
++ at: The new route's attributes
++*/
++extern int bgp_pgbgp_update (struct bgp_info *, struct attr *at,
++ struct bgp_node *);
++
++/*
++ bgp_pgbgp_rib_updated:
++ Call upon discovery of a new best path (or lack thereof)
++
++ This is a special case function for smoothly handling sub-prefix hijacks.
++
++ It handles the following 2 events:
++
++ Event 1: An anomalous sub-prefix is ignored, but no best route for the super-prefix exists
++ Response: Announce the sub-prefix until the super-prefix comes back
++
++ Event 2: A super-prefix comes back to the RIB and its anomalous sub-prefix is in use
++ Response: Ignore the sub-prefix again
++
++ Arguments:
++ rn: The route node that a new best path was found for
++ old_best: The old best route (NULL if one did not exist)
++ new_best: The current best route (NULL if one does not exist)
++ */
++extern int
++bgp_pgbgp_rib_updated (struct bgp_node *rn, struct bgp_info *old_best,
++ struct bgp_info *new_best);
++
++#endif
+--- a/bgpd/bgp_route.c
++++ b/bgpd/bgp_route.c
+@@ -51,6 +51,7 @@ Software Foundation, Inc., 59 Temple Pla
+ #include "bgpd/bgp_mplsvpn.h"
+ #include "bgpd/bgp_nexthop.h"
+ #include "bgpd/bgp_damp.h"
++#include "bgpd/bgp_pgbgp.h"
+ #include "bgpd/bgp_advertise.h"
+ #include "bgpd/bgp_zebra.h"
+ #include "bgpd/bgp_vty.h"
+@@ -339,12 +340,19 @@ bgp_info_cmp (struct bgp *bgp, struct bg
+
+ *paths_eq = 0;
+
++
+ /* 0. Null check. */
+ if (new == NULL)
+ return 0;
+ if (exist == NULL)
+ return 1;
+
++ /* 0.5 PGBGP Depref. Check */
++ if (ANOMALOUS(exist->flags) && !ANOMALOUS(new->flags))
++ return 1;
++ if (!ANOMALOUS(exist->flags) && ANOMALOUS(new->flags))
++ return 0;
++
+ /* 1. Weight check. */
+ if (new->attr->extra)
+ new_weight = new->attr->extra->weight;
+@@ -1583,6 +1591,10 @@ bgp_process_main (struct work_queue *wq,
+ UNSET_FLAG (new_select->flags, BGP_INFO_MULTIPATH_CHG);
+ }
+
++ /* PGBGP needs to know about selected routes */
++ if (CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP))
++ bgp_pgbgp_rib_updated(rn, old_select, new_select);
++
+
+ /* Check each BGP peer. */
+ for (ALL_LIST_ELEMENTS (bgp->peer, node, nnode, peer))
+@@ -1906,6 +1918,11 @@ bgp_update_rsclient (struct peer *rsclie
+ /* If the update is implicit withdraw. */
+ if (ri)
+ {
++ /* Update PGBGP state, and mark the route as anomalous if necessary */
++ if (CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP)
++ && peer_sort(peer) == BGP_PEER_EBGP)
++ bgp_pgbgp_update(ri, attr_new, rn);
++
+ ri->uptime = bgp_clock ();
+
+ /* Same attribute comes in. */
+@@ -2337,6 +2354,11 @@ bgp_update_main (struct peer *peer, stru
+ /* Increment prefix */
+ bgp_aggregate_increment (bgp, p, new, afi, safi);
+
++ /* Update PGBGP state, and mark the route as anomalous if necessary */
++ if (CHECK_FLAG (bgp->af_flags[afi][safi], BGP_CONFIG_PGBGP)
++ && peer_sort(peer) == BGP_PEER_EBGP)
++ bgp_pgbgp_update(new, attr_new, rn);
++
+ /* Register new BGP information. */
+ bgp_info_add (rn, new);
+
+@@ -5559,6 +5581,20 @@ enum bgp_display_type
+ static void
+ route_vty_short_status_out (struct vty *vty, struct bgp_info *binfo)
+ {
++ if (ANOMALOUS(binfo->flags))
++ {
++ vty_out(vty, "a[");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_P))
++ vty_out(vty, "i");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_O))
++ vty_out(vty, "p");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_E))
++ vty_out(vty, "e");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_IGNORED_P))
++ vty_out(vty, "s");
++ vty_out(vty, "] ");
++ }
++
+ /* Route status display. */
+ if (CHECK_FLAG (binfo->flags, BGP_INFO_REMOVED))
+ vty_out (vty, "R");
+@@ -6064,6 +6100,7 @@ route_vty_out_detail (struct vty *vty, s
+ }
+ \f
+ #define BGP_SHOW_SCODE_HEADER "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,%s r RIB-failure, S Stale, R Removed%s"
++#define BGP_SHOW_PCODE_HEADER "Status code: a (anomalous) of: [p] prefix hijack, [s] sub-prefix hijack,%s [i] informant of sub-prefix [e] new edge%s"
+ #define BGP_SHOW_OCODE_HEADER "Origin codes: i - IGP, e - EGP, ? - incomplete%s%s"
+ #define BGP_SHOW_HEADER " Network Next Hop Metric LocPrf Weight Path%s"
+ #define BGP_SHOW_DAMP_HEADER " Network From Reuse Path%s"
+@@ -6095,7 +6132,8 @@ enum bgp_show_type
+ bgp_show_type_flap_route_map,
+ bgp_show_type_flap_neighbor,
+ bgp_show_type_dampend_paths,
+- bgp_show_type_damp_neighbor
++ bgp_show_type_damp_neighbor,
++ bgp_show_type_anomalous_paths
+ };
+
+ static int
+@@ -6262,11 +6300,17 @@ bgp_show_table (struct vty *vty, struct
+ || CHECK_FLAG (ri->flags, BGP_INFO_HISTORY))
+ continue;
+ }
++ if (type == bgp_show_type_anomalous_paths)
++ {
++ if (! ANOMALOUS(ri->flags))
++ continue;
++ }
+
+ if (header)
+ {
+ vty_out (vty, "BGP table version is 0, local router ID is %s%s", inet_ntoa (*router_id), VTY_NEWLINE);
+ vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
++ vty_out (vty, BGP_SHOW_PCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+ vty_out (vty, BGP_SHOW_OCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+ if (type == bgp_show_type_dampend_paths
+ || type == bgp_show_type_damp_neighbor)
+@@ -6344,6 +6388,7 @@ bgp_show (struct vty *vty, struct bgp *b
+ return bgp_show_table (vty, table, &bgp->router_id, type, output_arg);
+ }
+
++
+ /* Header of detailed BGP route information */
+ static void
+ route_vty_out_detail_header (struct vty *vty, struct bgp *bgp,
+@@ -11904,6 +11949,64 @@ DEFUN (bgp_damp_set,
+ half, reuse, suppress, max);
+ }
+
++DEFUN (bgp_pgbgp_arg,
++ bgp_pgbgp_arg_cmd,
++ "bgp pgbgp <1-100> <1-100> <1-100> <1-365> <1-365> <1-365> WORD WORD",
++ "BGP Specific commands\n"
++ "Enable Pretty Good BGP\n"
++ "New origin depref time (in hours)\n"
++ "New edge depref time (in hours)\n"
++ "New sub-prefix depref time (in hours)\n"
++ "Origin history time (in days)\n"
++ "Prefix history time (in days)\n"
++ "Edge history time (in days)\n"
++ "Log file for history data\n"
++ "Log file of anomalies\n")
++{
++ struct bgp *bgp;
++
++ int ost = DEFAULT_ORIGIN_SUS;
++ int est = DEFAULT_EDGE_SUS;
++ int sst = DEFAULT_SUB_SUS;
++ int oht = DEFAULT_ORIGIN_HIST;
++ int pht = DEFAULT_PREFIX_HIST;
++ int eht = DEFAULT_EDGE_HIST;
++ const char* path = "/var/log/quagga/pgbgp_hist";
++ const char* anoms = "/var/log/quagga/pgbgp_anomalies";
++
++ if (argc == 8)
++ {
++ VTY_GET_INTEGER("origin depref time", ost, argv[0]);
++ VTY_GET_INTEGER("edge depref time", est, argv[1]);
++ VTY_GET_INTEGER("sub-prefix depref time", sst, argv[2]);
++ VTY_GET_INTEGER("origin history time", oht, argv[3]);
++ VTY_GET_INTEGER("prefix history time", pht, argv[4]);
++ VTY_GET_INTEGER("edge history time", eht, argv[5]);
++ path = argv[6];
++ anoms = argv[7];
++ }
++
++ bgp = vty->index;
++ return bgp_pgbgp_enable(bgp, bgp_node_afi (vty), bgp_node_safi (vty),
++ ost, est, sst, oht, pht, eht, path, anoms);
++}
++
++ALIAS (bgp_pgbgp_arg,
++ bgp_pgbgp_cmd,
++ "bgp pgbgp",
++ "BGP specific commands\n"
++ "Enable Pretty Good BGP\n")
++
++DEFUN (bgp_pgbgp_unset,
++ bgp_pgbgp_unset_cmd,
++ "no bgp pgbgp\n",
++ "BGP specific commands\n")
++{
++ struct bgp *bgp;
++ bgp = vty->index;
++ return bgp_pgbgp_disable (bgp, bgp_node_afi (vty), bgp_node_safi (vty));
++}
++
+ ALIAS (bgp_damp_set,
+ bgp_damp_set2_cmd,
+ "bgp dampening <1-45>",
+@@ -11953,6 +12056,19 @@ DEFUN (show_ip_bgp_dampened_paths,
+ NULL);
+ }
+
++DEFUN (show_ip_bgp_anomalous_paths,
++ show_ip_bgp_anomalous_paths_cmd,
++ "show ip bgp anomalous-paths",
++ SHOW_STR
++ IP_STR
++ BGP_STR
++ "Display anomalous paths (less likely to be used)\n")
++{
++ return bgp_show (vty, NULL, AFI_IP, SAFI_UNICAST, bgp_show_type_anomalous_paths,
++ NULL);
++}
++
++
+ DEFUN (show_ip_bgp_flap_statistics,
+ show_ip_bgp_flap_statistics_cmd,
+ "show ip bgp flap-statistics",
+@@ -12479,6 +12595,7 @@ bgp_route_init (void)
+ install_element (VIEW_NODE, &show_ip_bgp_neighbor_received_prefix_filter_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_ipv4_neighbor_received_prefix_filter_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_dampened_paths_cmd);
++ install_element (VIEW_NODE, &show_ip_bgp_anomalous_paths_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_flap_statistics_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_flap_address_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_flap_prefix_cmd);
+@@ -12612,6 +12729,7 @@ bgp_route_init (void)
+ install_element (ENABLE_NODE, &show_ip_bgp_neighbor_received_prefix_filter_cmd);
+ install_element (ENABLE_NODE, &show_ip_bgp_ipv4_neighbor_received_prefix_filter_cmd);
+ install_element (ENABLE_NODE, &show_ip_bgp_dampened_paths_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_anomalous_paths_cmd);
+ install_element (ENABLE_NODE, &show_ip_bgp_flap_statistics_cmd);
+ install_element (ENABLE_NODE, &show_ip_bgp_flap_address_cmd);
+ install_element (ENABLE_NODE, &show_ip_bgp_flap_prefix_cmd);
+@@ -13002,6 +13120,10 @@ bgp_route_init (void)
+ install_element (BGP_IPV4_NODE, &bgp_damp_unset_cmd);
+ install_element (BGP_IPV4_NODE, &bgp_damp_unset2_cmd);
+
++ install_element (BGP_NODE, &bgp_pgbgp_cmd);
++ install_element (BGP_NODE, &bgp_pgbgp_arg_cmd);
++ install_element (BGP_NODE, &bgp_pgbgp_unset_cmd);
++
+ /* Deprecated AS-Pathlimit commands */
+ install_element (BGP_NODE, &bgp_network_ttl_cmd);
+ install_element (BGP_NODE, &bgp_network_mask_ttl_cmd);
+--- a/bgpd/bgp_route.h
++++ b/bgpd/bgp_route.h
+@@ -1,3 +1,4 @@
++
+ /* BGP routing information base
+ Copyright (C) 1996, 97, 98, 2000 Kunihiro Ishiguro
+
+@@ -68,7 +69,7 @@ struct bgp_info
+ int lock;
+
+ /* BGP information status. */
+- u_int16_t flags;
++ u_int32_t flags;
+ #define BGP_INFO_IGP_CHANGED (1 << 0)
+ #define BGP_INFO_DAMPED (1 << 1)
+ #define BGP_INFO_HISTORY (1 << 2)
+@@ -82,6 +83,10 @@ struct bgp_info
+ #define BGP_INFO_COUNTED (1 << 10)
+ #define BGP_INFO_MULTIPATH (1 << 11)
+ #define BGP_INFO_MULTIPATH_CHG (1 << 12)
++#define BGP_INFO_SUSPICIOUS_O (1 << 13)
++#define BGP_INFO_SUSPICIOUS_P (1 << 14)
++#define BGP_INFO_IGNORED_P (1 << 15)
++#define BGP_INFO_SUSPICIOUS_E (1 << 16)
+
+ /* BGP route type. This can be static, RIP, OSPF, BGP etc. */
+ u_char type;
+@@ -126,7 +131,7 @@ struct bgp_static
+
+ /* Flags which indicate a route is unuseable in some form */
+ #define BGP_INFO_UNUSEABLE \
+- (BGP_INFO_HISTORY|BGP_INFO_DAMPED|BGP_INFO_REMOVED)
++ (BGP_INFO_HISTORY|BGP_INFO_DAMPED|BGP_INFO_REMOVED|BGP_INFO_IGNORED_P)
+ /* Macro to check BGP information is alive or not. Sadly,
+ * not equivalent to just checking previous, because of the
+ * sense of the additional VALID flag.
+--- a/bgpd/bgp_table.h
++++ b/bgpd/bgp_table.h
+@@ -65,6 +65,8 @@ struct bgp_node
+
+ int lock;
+
++ struct bgp_pgbgp_hist *hist;
++
+ u_char flags;
+ #define BGP_NODE_PROCESS_SCHEDULED (1 << 0)
+ };
+--- a/bgpd/bgpd.h
++++ b/bgpd/bgpd.h
+@@ -123,6 +123,7 @@ struct bgp
+ /* BGP Per AF flags */
+ u_int16_t af_flags[AFI_MAX][SAFI_MAX];
+ #define BGP_CONFIG_DAMPENING (1 << 0)
++#define BGP_CONFIG_PGBGP (1 << 1)
+
+ /* Static route configuration. */
+ struct bgp_table *route[AFI_MAX][SAFI_MAX];
+--- a/lib/hash.c
++++ b/lib/hash.c
+@@ -166,6 +166,35 @@ hash_iterate (struct hash *hash,
+ }
+ }
+
++/*
++ Iterates until 0 is returned or until completion
++ Return: 1 if iteration completed
++ Return: 0 if iteration was interrupted
++*/
++
++int
++hash_iterate_until(struct hash *hash,
++ int (*func) (struct hash_backet *, void *), void *arg)
++{
++ unsigned int i;
++ struct hash_backet *hb;
++ struct hash_backet *hbnext;
++ int ret;
++
++ for (i = 0; i < hash->size; i++)
++ for (hb = hash->index[i]; hb; hb = hbnext)
++ {
++ /* get pointer to next hash backet here, in case (*func)
++ * decides to delete hb by calling hash_release
++ */
++ hbnext = hb->next;
++ ret = (*func) (hb, arg);
++ if (!ret)
++ return 0;
++ }
++ return 1;
++}
++
+ /* Clean up hash. */
+ void
+ hash_clean (struct hash *hash, void (*free_func) (void *))
+--- a/lib/hash.h
++++ b/lib/hash.h
+@@ -66,7 +66,8 @@ extern void *hash_release (struct hash *
+
+ extern void hash_iterate (struct hash *,
+ void (*) (struct hash_backet *, void *), void *);
+-
++extern int hash_iterate_until(struct hash *,
++ int (*) (struct hash_backet *, void *), void *);
+ extern void hash_clean (struct hash *, void (*) (void *));
+ extern void hash_free (struct hash *);
+
+--- a/lib/memtypes.c
++++ b/lib/memtypes.c
+@@ -148,6 +148,15 @@ struct memory_list memory_list_bgp[] =
+ { MTYPE_PEER_UPDATE_SOURCE, "BGP peer update interface" },
+ { MTYPE_BGP_DAMP_INFO, "Dampening info" },
+ { MTYPE_BGP_DAMP_ARRAY, "BGP Dampening array" },
++ { 0, NULL },
++ { MTYPE_BGP_PGBGP_ORIGIN, "BGP PGBGP Origin AS Node" },
++ { MTYPE_BGP_PGBGP_PREFIX, "BGP PGBGP Prefix AS Node" },
++ { MTYPE_BGP_PGBGP_EDGE, "BGP PGBGP Edge Node" },
++ { MTYPE_BGP_PGBGP_REUSE, "BGP PGBGP Reuse Node" },
++ { MTYPE_BGP_PGBGP_HIST, "BGP PGBGP History Node" },
++ { MTYPE_BGP_PGBGP_AVOID, "BGP PGBGP Avoid Peer Node" },
++ { MTYPE_BGP_PGBGP_PEER, "BGP PGBGP Peer Timing" },
++ { 0, NULL },
+ { MTYPE_BGP_REGEXP, "BGP regexp" },
+ { MTYPE_BGP_AGGREGATE, "BGP aggregate" },
+ { -1, NULL }
--- /dev/null
+From: Paul Jakma <paul.jakma@sun.com>
+Date: Thu, 4 Sep 2008 22:27:13 +0000 (+0100)
+Subject: [bgp/pgbgp] Add some pgbgp commands to restricted-mode and other command tweaks
+X-Git-Url: http://git.ozo.com/?p=quagga-pgbg.git;a=commitdiff_plain;h=06ac72f9f6021635e9e1e5105c3e22bf7eb0d6c3
+
+[bgp/pgbgp] Add some pgbgp commands to restricted-mode and other command tweaks
+
+* bgp_pgbgp.c:
+ (edge_neighbor_iterator) make ASN==0 mean 'iterate over all ASNs'
+ (bgp_pgbgp_stats_origin_one) new function, to display one origin AS status.
+ (bgp_pgbgp_stats_origins) adapt to use previous.
+ Adapt to iterate over all stats if no prefix was giving.
+ (show_ip_bgp_pgbgp_neighbors_cmd) recognise no ASN argument case
+ (show_ip_bgp_pgbgp_neighbors_all_cmd) Iterate over all
+ (show_ip_bgp_pgbgp_origins_cmd) similar
+ (show_ip_bgp_pgbgp_origins_all_cmd)
+ (bgp_pgbgp_enable) install the lookup commands to ther new RESTRICTED_NODE
+* bgp_route.c:
+ (route_vty_short_status_out) only allowed to print one char for anomalous
+ status.
+ (route_vty_out_detail) Add support for printing out more detail on
+ PG-BGP status
+---
+
+--- a/bgpd/bgp_pgbgp.c
++++ b/bgpd/bgp_pgbgp.c
+@@ -227,7 +227,7 @@ static void
+ edge_neighbor_iterator (struct hash_backet *backet, struct nsearch *pns)
+ {
+ struct bgp_pgbgp_edge *hedge = backet->data;
+- if ((hedge->e.a == pns->asn || hedge->e.b == pns->asn)
++ if ((!pns->asn || hedge->e.a == pns->asn || hedge->e.b == pns->asn)
+ && hedge->e.a != hedge->e.b)
+ {
+ struct vty *vty = pns->pvty;
+@@ -254,13 +254,39 @@ bgp_pgbgp_stats_neighbors (struct vty *v
+ return CMD_SUCCESS;
+ }
+
++static void
++bgp_pgbgp_stats_origin_one (struct vty *vty, struct bgp_node *rn,
++ time_t t_now)
++{
++ char str[INET6_BUFSIZ];
++
++ if (!rn->hist)
++ return;
++
++ prefix2str (&rn->p, str, sizeof(str));
++ vty_out (vty, "%s%s", str, VTY_NEWLINE);
++
++ for (struct bgp_pgbgp_origin * cur = rn->hist->o; cur != NULL;
++ cur = cur->next)
++ {
++ if (cur->deprefUntil > t_now)
++ vty_out (vty, "Untrusted Origin AS: %d%s", cur->originAS,
++ VTY_NEWLINE);
++ else
++ vty_out (vty, "Trusted Origin AS: %d%s", cur->originAS,
++ VTY_NEWLINE);
++ }
++}
++
+ static int
+ bgp_pgbgp_stats_origins (struct vty *vty, afi_t afi, safi_t safi,
+ const char *prefix)
+ {
+ struct bgp *bgp;
+ struct bgp_table *table;
++ struct bgp_node *rn;
+ time_t t_now = time (NULL);
++
+ bgp = bgp_get_default ();
+ if (bgp == NULL)
+ return CMD_WARNING;
+@@ -269,28 +295,22 @@ bgp_pgbgp_stats_origins (struct vty *vty
+ table = bgp->rib[afi][safi];
+ if (table == NULL)
+ return CMD_WARNING;
+-
+- struct prefix p;
+- str2prefix (prefix, &p);
+- struct bgp_node *rn = bgp_node_match (table, &p);
+- vty_out (vty, "%s%s", prefix, VTY_NEWLINE);
+- if (rn)
++
++ if (prefix)
+ {
++ struct prefix p;
++ str2prefix (prefix, &p);
++ rn = bgp_node_match (table, &p);
+ if (rn->hist)
+- {
+- for (struct bgp_pgbgp_origin * cur = rn->hist->o; cur != NULL;
+- cur = cur->next)
+- {
+- if (cur->deprefUntil > t_now)
+- vty_out (vty, "Untrusted Origin AS: %d%s", cur->originAS,
+- VTY_NEWLINE);
+- else
+- vty_out (vty, "Trusted Origin AS: %d%s", cur->originAS,
+- VTY_NEWLINE);
+- }
+- }
++ bgp_pgbgp_stats_origin_one (vty, rn, t_now);
+ bgp_unlock_node (rn);
++ return CMD_SUCCESS;
+ }
++
++ for (rn = bgp_table_top (table); rn; rn = bgp_route_next (rn))
++ if (rn->hist)
++ bgp_pgbgp_stats_origin_one (vty, rn, t_now);
++
+ return CMD_SUCCESS;
+ }
+
+@@ -377,7 +397,7 @@ bgp_pgbgp_stats (struct vty *vty, afi_t
+ DEFUN (show_ip_bgp_pgbgp,
+ show_ip_bgp_pgbgp_cmd,
+ "show ip bgp pgbgp",
+- SHOW_STR IP_STR BGP_STR "Display PGBGP statistics\n")
++ SHOW_STR IP_STR BGP_STR "Pretty-Good BGP statistics\n")
+ {
+ return bgp_pgbgp_stats (vty, AFI_IP, SAFI_UNICAST);
+ }
+@@ -385,29 +405,46 @@ DEFUN (show_ip_bgp_pgbgp,
+ DEFUN (show_ip_bgp_pgbgp_neighbors,
+ show_ip_bgp_pgbgp_neighbors_cmd,
+ "show ip bgp pgbgp neighbors WORD",
+- SHOW_STR
+- IP_STR
+- BGP_STR
+- "BGP pgbgp\n"
+- "BGP pgbgp neighbors\n" "ASN whos neighbors should be displayed\n")
++ SHOW_STR IP_STR BGP_STR
++ "Pretty-Good BGP statistics\n"
++ "PG-BGP neighbor information\n"
++ "AS to show neighbors of\n")
+ {
+ return bgp_pgbgp_stats_neighbors (vty, AFI_IP, SAFI_UNICAST,
+- atoi (argv[0]));
++ argc == 1 ? atoi (argv[0]) : 0);
+ }
+
++ALIAS (show_ip_bgp_pgbgp_neighbors,
++ show_ip_bgp_pgbgp_neighbors_all_cmd,
++ "show ip bgp pgbgp neighbors",
++ SHOW_STR
++ IP_STR
++ BGP_STR
++ "Pretty-Good BGP statistics\n"
++ "PG-BGP neighbors information\n")
++
+ DEFUN (show_ip_bgp_pgbgp_origins,
+ show_ip_bgp_pgbgp_origins_cmd,
+ "show ip bgp pgbgp origins A.B.C.D/M",
+ SHOW_STR
+ IP_STR
+ BGP_STR
+- "BGP pgbgp\n"
+- "BGP pgbgp neighbors\n" "Prefix to look up origin ASes of\n")
++ "Pretty-Good BGP statistics\n"
++ "PG-BGP prefix origin information\n"
++ "Prefix to look up origin ASes of\n")
+ {
+- return bgp_pgbgp_stats_origins (vty, AFI_IP, SAFI_UNICAST, argv[0]);
++ return bgp_pgbgp_stats_origins (vty, AFI_IP, SAFI_UNICAST,
++ argc == 1 ? argv[0] : NULL);
+ }
+
+-
++ALIAS (show_ip_bgp_pgbgp_origins,
++ show_ip_bgp_pgbgp_origins_all_cmd,
++ "show ip bgp pgbgp origins",
++ SHOW_STR
++ IP_STR
++ BGP_STR
++ "Pretty-Good BGP statistics\n"
++ "PG-BGP prefixes origin information")
+
+
+ /*! --------------- VTY (others exist in bgp_route.c) ------------------ !*/
+@@ -749,12 +786,19 @@ bgp_pgbgp_enable (struct bgp *bgp, afi_t
+ pgbgp->lastgc = time (NULL);
+ pgbgp->lastStore = time (NULL);
+ pgbgp->startTime = time (NULL);
++ install_element (RESTRICTED_NODE, &show_ip_bgp_pgbgp_cmd);
++ install_element (RESTRICTED_NODE, &show_ip_bgp_pgbgp_neighbors_cmd);
++ install_element (RESTRICTED_NODE, &show_ip_bgp_pgbgp_origins_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_cmd);
+- install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_neighbors_cmd);
+- install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_neighbors_cmd);
+ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_origins_cmd);
++ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_neighbors_all_cmd);
++ install_element (VIEW_NODE, &show_ip_bgp_pgbgp_origins_all_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_neighbors_cmd);
+ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_origins_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_neighbors_all_cmd);
++ install_element (ENABLE_NODE, &show_ip_bgp_pgbgp_origins_all_cmd);
+ pgbgp->edgeT = hash_create_size (131072, edge_key_make, edge_cmp);
+ bgp_pgbgp_restore ();
+ return 0;
+--- a/bgpd/bgp_route.c
++++ b/bgpd/bgp_route.c
+@@ -5581,20 +5581,6 @@ enum bgp_display_type
+ static void
+ route_vty_short_status_out (struct vty *vty, struct bgp_info *binfo)
+ {
+- if (ANOMALOUS(binfo->flags))
+- {
+- vty_out(vty, "a[");
+- if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_P))
+- vty_out(vty, "i");
+- if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_O))
+- vty_out(vty, "p");
+- if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_E))
+- vty_out(vty, "e");
+- if (CHECK_FLAG(binfo->flags, BGP_INFO_IGNORED_P))
+- vty_out(vty, "s");
+- vty_out(vty, "] ");
+- }
+-
+ /* Route status display. */
+ if (CHECK_FLAG (binfo->flags, BGP_INFO_REMOVED))
+ vty_out (vty, "R");
+@@ -5610,6 +5596,17 @@ route_vty_short_status_out (struct vty *
+ /* Selected */
+ if (CHECK_FLAG (binfo->flags, BGP_INFO_HISTORY))
+ vty_out (vty, "h");
++ else if (ANOMALOUS(binfo->flags))
++ {
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_O))
++ vty_out(vty, "p");
++ else if (CHECK_FLAG(binfo->flags, BGP_INFO_IGNORED_P))
++ vty_out(vty, "P");
++ else if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_P))
++ vty_out(vty, "a");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_E))
++ vty_out(vty, "a");
++ }
+ else if (CHECK_FLAG (binfo->flags, BGP_INFO_DAMPED))
+ vty_out (vty, "d");
+ else if (CHECK_FLAG (binfo->flags, BGP_INFO_SELECTED))
+@@ -6088,7 +6085,22 @@ route_vty_out_detail (struct vty *vty, s
+ if (binfo->extra && binfo->extra->damp_info)
+ bgp_damp_info_vty (vty, binfo);
+
+- /* Line 7 display Uptime */
++ /* 8: PGBGP status */
++ if (ANOMALOUS(binfo->flags))
++ {
++ vty_out (vty, " Anomalous:");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_P))
++ vty_out (vty, " divergent sub-prefixes,");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_O))
++ vty_out (vty, " origin AS (prefix hijack?),");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_SUSPICIOUS_E))
++ vty_out (vty, " new edge in path,");
++ if (CHECK_FLAG(binfo->flags, BGP_INFO_IGNORED_P))
++ vty_out (vty, " origin AS (sub-prefix hijack?),");
++ vty_out (vty, "%s", VTY_NEWLINE);
++ }
++
++ /* Line 9 display Uptime */
+ #ifdef HAVE_CLOCK_MONOTONIC
+ tbuf = time(NULL) - (bgp_clock() - binfo->uptime);
+ vty_out (vty, " Last update: %s", ctime(&tbuf));
+@@ -6099,8 +6111,9 @@ route_vty_out_detail (struct vty *vty, s
+ vty_out (vty, "%s", VTY_NEWLINE);
+ }
+ \f
+-#define BGP_SHOW_SCODE_HEADER "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,%s r RIB-failure, S Stale, R Removed%s"
+-#define BGP_SHOW_PCODE_HEADER "Status code: a (anomalous) of: [p] prefix hijack, [s] sub-prefix hijack,%s [i] informant of sub-prefix [e] new edge%s"
++#define BGP_SHOW_SCODE_HEADER "Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,%s" \
++ " r RIB-failure, S Stale, R Removed, %s" \
++ " p prefix hijack, P sub-prefix hijack, a other anomaly%s"
+ #define BGP_SHOW_OCODE_HEADER "Origin codes: i - IGP, e - EGP, ? - incomplete%s%s"
+ #define BGP_SHOW_HEADER " Network Next Hop Metric LocPrf Weight Path%s"
+ #define BGP_SHOW_DAMP_HEADER " Network From Reuse Path%s"
+@@ -6309,8 +6322,7 @@ bgp_show_table (struct vty *vty, struct
+ if (header)
+ {
+ vty_out (vty, "BGP table version is 0, local router ID is %s%s", inet_ntoa (*router_id), VTY_NEWLINE);
+- vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+- vty_out (vty, BGP_SHOW_PCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
++ vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ vty_out (vty, BGP_SHOW_OCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+ if (type == bgp_show_type_dampend_paths
+ || type == bgp_show_type_damp_neighbor)
+@@ -9842,7 +9854,7 @@ show_adj_route (struct vty *vty, struct
+ PEER_STATUS_DEFAULT_ORIGINATE))
+ {
+ vty_out (vty, "BGP table version is 0, local router ID is %s%s", inet_ntoa (bgp->router_id), VTY_NEWLINE);
+- vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
++ vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ vty_out (vty, BGP_SHOW_OCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out (vty, "Originating default network 0.0.0.0%s%s",
+@@ -9859,7 +9871,7 @@ show_adj_route (struct vty *vty, struct
+ if (header1)
+ {
+ vty_out (vty, "BGP table version is 0, local router ID is %s%s", inet_ntoa (bgp->router_id), VTY_NEWLINE);
+- vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
++ vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ vty_out (vty, BGP_SHOW_OCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+ header1 = 0;
+ }
+@@ -9883,7 +9895,7 @@ show_adj_route (struct vty *vty, struct
+ if (header1)
+ {
+ vty_out (vty, "BGP table version is 0, local router ID is %s%s", inet_ntoa (bgp->router_id), VTY_NEWLINE);
+- vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
++ vty_out (vty, BGP_SHOW_SCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+ vty_out (vty, BGP_SHOW_OCODE_HEADER, VTY_NEWLINE, VTY_NEWLINE);
+ header1 = 0;
+ }
--- /dev/null
+--- a/vtysh/vtysh.c
++++ b/vtysh/vtysh.c
+@@ -269,7 +269,7 @@ vtysh_pager_init (void)
+ if (pager_defined)
+ vtysh_pager_name = strdup (pager_defined);
+ else
+- vtysh_pager_name = strdup ("more");
++ vtysh_pager_name = strdup ("cat");
+ }
+
+ /* Command execution over the vty interface. */
+@@ -1885,7 +1885,7 @@ DEFUN (vtysh_terminal_length,
+ {
+ int lines;
+ char *endptr = NULL;
+- char default_pager[10];
++ char default_pager[12];
+
+ lines = strtol (argv[0], &endptr, 10);
+ if (lines < 0 || lines > 512 || *endptr != '\0')
+@@ -1902,7 +1902,7 @@ DEFUN (vtysh_terminal_length,
+
+ if (lines != 0)
+ {
+- snprintf(default_pager, 10, "more -%i", lines);
++ snprintf(default_pager, 12, "head -n %i", lines);
+ vtysh_pager_name = strdup (default_pager);
+ }
+