From aa2d79d49b7b55e7169664c502703feda598d7ef Mon Sep 17 00:00:00 2001 From: Denis Akimov Date: Thu, 8 Jun 2017 19:23:25 +0500 Subject: [PATCH 1/2] addition to #43 - demo NodeJS webapp --- samples/screen-sharing/cert.pem | 22 + samples/screen-sharing/csr.pem | 18 + samples/screen-sharing/css/bootstrap.min.css | 6 + samples/screen-sharing/favicon.ico | Bin 0 -> 1150 bytes samples/screen-sharing/img/female_head.png | Bin 0 -> 4725 bytes samples/screen-sharing/img/man_head.png | Bin 0 -> 20333 bytes .../img/telestax_logo_white.png | Bin 0 -> 1611 bytes samples/screen-sharing/index.html | 66 + samples/screen-sharing/js/client.js | 209 + samples/screen-sharing/js/lib/WebRTComm.js | 5058 +++ samples/screen-sharing/js/lib/adapter.js | 1574 + .../screen-sharing/js/lib/bootstrap.min.js | 7 + samples/screen-sharing/js/lib/jain-sip.js | 32108 ++++++++++++++++ samples/screen-sharing/js/lib/jquery.min.js | 4 + samples/screen-sharing/key.pem | 27 + .../screen-sharing-extension.crx | Bin 0 -> 3788 bytes samples/screen-sharing/server-secure.js | 13 + samples/screen-sharing/server.js | 8 + 18 files changed, 39120 insertions(+) create mode 100644 samples/screen-sharing/cert.pem create mode 100644 samples/screen-sharing/csr.pem create mode 100644 samples/screen-sharing/css/bootstrap.min.css create mode 100644 samples/screen-sharing/favicon.ico create mode 100644 samples/screen-sharing/img/female_head.png create mode 100644 samples/screen-sharing/img/man_head.png create mode 100644 samples/screen-sharing/img/telestax_logo_white.png create mode 100644 samples/screen-sharing/index.html create mode 100644 samples/screen-sharing/js/client.js create mode 100644 samples/screen-sharing/js/lib/WebRTComm.js create mode 100644 samples/screen-sharing/js/lib/adapter.js create mode 100644 samples/screen-sharing/js/lib/bootstrap.min.js create mode 100644 samples/screen-sharing/js/lib/jain-sip.js create mode 100644 samples/screen-sharing/js/lib/jquery.min.js create mode 100644 samples/screen-sharing/key.pem create mode 100644 samples/screen-sharing/screen-sharing-extension.crx create mode 100644 samples/screen-sharing/server-secure.js create mode 100644 samples/screen-sharing/server.js diff --git a/samples/screen-sharing/cert.pem b/samples/screen-sharing/cert.pem new file mode 100644 index 0000000..970fb14 --- /dev/null +++ b/samples/screen-sharing/cert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqjCCApICCQCySgN7jKzm9zANBgkqhkiG9w0BAQsFADCBljELMAkGA1UEBhMC +R1IxDzANBgNVBAgMBkF0dGlraTEPMA0GA1UEBwwGQXRoZW5zMREwDwYDVQQKDAhU +ZWxlc3RheDENMAsGA1UECwwEVW5pdDETMBEGA1UEAwwKdGVzdC5sb2NhbDEuMCwG +CSqGSIb3DQEJARYfYW50b25pcy50c2FraXJpZGlzQHRlbGVzdGF4LmNvbTAeFw0x +NTEyMDcxNjMxMTBaFw00MzA0MjMxNjMxMTBaMIGWMQswCQYDVQQGEwJHUjEPMA0G +A1UECAwGQXR0aWtpMQ8wDQYDVQQHDAZBdGhlbnMxETAPBgNVBAoMCFRlbGVzdGF4 +MQ0wCwYDVQQLDARVbml0MRMwEQYDVQQDDAp0ZXN0LmxvY2FsMS4wLAYJKoZIhvcN +AQkBFh9hbnRvbmlzLnRzYWtpcmlkaXNAdGVsZXN0YXguY29tMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoyn8W+rm16BrPQjLQgjpsHB6fy0+KmtyM519 +BnM9IvUydyn/iaW6gjqow4gzN4iCYiSyq4WwsR2kqwwm0P3yR2w+yj3HcuWNcA0T +1v1vewEb8Y69T/OkgLpCS86599CtR1aIhPQ3H41L47HzlwTWvu87Ko436pwOVhZ7 +vwKQuV5S4VMYxhilAAv9LcWQo+f6yAE+g8gc9s5xspRLBvgbbO2ZtQY+DT9SckHZ +/IlsmjdwcrsuxqLQrchamECuz2dIEbUxowdVfk3l73RSQa0LcOQg9AgkjSrLwlHo +2wboozQp2kqWm8w9w02G9gjLqoWRsEhYByKTDwDPOa0FpbIWmwIDAQABMA0GCSqG +SIb3DQEBCwUAA4IBAQBRFkLNtRANmGhihg8VRbdJeZaJ1i6RjhdWQLnz30ivDVF2 +KeOIYg2sjitdsDGvMZz9Yfwx6Nlx6rpQIMDGXqG/hkIEdJ3LThNS1oZzSdFVEynn +ha6VSGR3WyvCepQlHsKGz8jF0frCf+x6NVz+AOBV0itwFYoFIjvFnidUjQiCtq8f +Av18eCRlIdSHFjc8iMTgRw4lAwZ+kDITbY6iiDVZiMmCbTrs4tNTOyJIRXjUF9aV +7rh7Hrf1Q3TGJPbjynQLuJz7ay0BCZ1MhzXLB2/b7/DtNngr/RyLeNv6k0qUt9Hw +5qgcAxtVLZg601Lz+5FbhvT11AoqdeJWykoFRJSQ +-----END CERTIFICATE----- diff --git a/samples/screen-sharing/csr.pem b/samples/screen-sharing/csr.pem new file mode 100644 index 0000000..4604495 --- /dev/null +++ b/samples/screen-sharing/csr.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC9TCCAd0CAQAwgZYxCzAJBgNVBAYTAkdSMQ8wDQYDVQQIDAZBdHRpa2kxDzAN +BgNVBAcMBkF0aGVuczERMA8GA1UECgwIVGVsZXN0YXgxDTALBgNVBAsMBFVuaXQx +EzARBgNVBAMMCnRlc3QubG9jYWwxLjAsBgkqhkiG9w0BCQEWH2FudG9uaXMudHNh +a2lyaWRpc0B0ZWxlc3RheC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCjKfxb6ubXoGs9CMtCCOmwcHp/LT4qa3IznX0Gcz0i9TJ3Kf+JpbqCOqjD +iDM3iIJiJLKrhbCxHaSrDCbQ/fJHbD7KPcdy5Y1wDRPW/W97ARvxjr1P86SAukJL +zrn30K1HVoiE9DcfjUvjsfOXBNa+7zsqjjfqnA5WFnu/ApC5XlLhUxjGGKUAC/0t +xZCj5/rIAT6DyBz2znGylEsG+Bts7Zm1Bj4NP1JyQdn8iWyaN3Byuy7GotCtyFqY +QK7PZ0gRtTGjB1V+TeXvdFJBrQtw5CD0CCSNKsvCUejbBuijNCnaSpabzD3DTYb2 +CMuqhZGwSFgHIpMPAM85rQWlshabAgMBAAGgGTAXBgkqhkiG9w0BCQIxCgwIVGVs +ZXN0YXgwDQYJKoZIhvcNAQELBQADggEBAIIqxvWJH3ZiLc9hRc0pzQAwaIAmNrhQ +0U7sjLMjNjbzEQ/PXQcCjeggCDysVrz8JsLUhqOtnhnZg/9061FSDoI0ppPvcFtV +snokgQl2NvLu3OZfSEhIxb8vJq7iUJkXNpnmr/zAVqASJ9mjyCK8hZ3a68Zv+HSx +wNwMZ60EsacJn93vjCm10g2EEDaaSyvigvQjHr7QqIyf5HhqTeWxcnztGT1SOfC7 +IzAOCtmSJmj3KiHG5WYjlJvkIod4jBjO1okbzoe2AG4FG13dGeDj1W2WMnM/9Gon +DBMvII6asXbTzVJE3McBE/ezNvPB+etpx3aaOW4POtULm9AvA0hNLSw= +-----END CERTIFICATE REQUEST----- diff --git a/samples/screen-sharing/css/bootstrap.min.css b/samples/screen-sharing/css/bootstrap.min.css new file mode 100644 index 0000000..ed3905e --- /dev/null +++ b/samples/screen-sharing/css/bootstrap.min.css @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\002a"}.glyphicon-plus:before{content:"\002b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-btc:before{content:"\e227"}.glyphicon-xbt:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-jpy:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-rub:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role=button]{cursor:pointer}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:focus,a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:focus,a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:focus,a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:focus,a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:focus,a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:focus,a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40}.btn-primary:hover{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-object.img-thumbnail{max-width:none}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;filter:alpha(opacity=0);opacity:0;line-break:auto}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-style:normal;font-weight:400;line-height:1.42857143;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);line-break:auto}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table!important}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table!important}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table!important}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table!important}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table!important}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/samples/screen-sharing/favicon.ico b/samples/screen-sharing/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3ad82b0d2d0f23912a181d4eb9c2d3a61108ec0d GIT binary patch literal 1150 zcmbW#PbfrD6vy#13=997gjmgJ{FxF93n|LV(oR`WHWn84R{pJQS&~p{NUSZCC>z<@ zDwHfNu#<)H{k?Z@2F<*enLh8_d(S=ho_UQ~f^RBiJTo?%G;1-l99Uu{Tql^3Ype|g z^D>Xz`E`+K_?Z(;|-7{M6y-qO&%Ec%etyr|~{^9`tH*+(-j z+X3f_hF;?GKZtak$_i%$3jH0++y{WGQ97E@8uKwrH1?}m@5PtlZ zTuyoYUt}I{I6)JFyP_W22fhD1^jjVg+#OY`JbS$X##@-dpo?w9$YZppNFC!dsFwx5 z9(l-jFM1bixW&Xj>eWjyp_W&a-CAl_8C_rkwVOYE=6LybKsL literal 0 HcmV?d00001 diff --git a/samples/screen-sharing/img/female_head.png b/samples/screen-sharing/img/female_head.png new file mode 100644 index 0000000000000000000000000000000000000000..41cfa6b6780fee1cfdd07d3f5816a6da216537ac GIT binary patch literal 4725 zcmZ`-XH=6-x5Ww;x53#0dPk9jjs!v{APE8KErFM+0v5;|z> z%L2P;6T!m5ea}e$Hu9h8wb_W|@ujG~%{}#qj_Q^l#8pUDPzvP?!oD;7ocLK>%qw?b zt@}cud7Or47VW<8hr*mm6{4_^Hv{*|+&$C$Rc5AnMP_%IwfLIdEsI*Nlo)$1|A#X- zTmCHkQ`10gSvjIjZSU(uw7*^v4MCAMwzD}qR<|Rfp-y^IJEk5gy!Q(p^YxAya(k%J zqC>W?jJ_*{j8&#LvoxvUhhN4LGDiL>yKbT`Efbaz5oV1lrd+Wd=VTsy78CFyX>!C7_kQ3odr`7jZQz`FP$D&Z{?aTX(en}- z_*xsx>z#22wm27<`yu<-TC?wZ;!lp2sUE(NK!;hFY7N?S!8s%dLd>yC4S5o!)w@G4 z(PY4l0hRXNL~eq`UClv^scq;4F-}^gib$0ZDbQd2+R>;tizPWOIrZIDPb+4V`eY8V z4V_ss2GozhqRZ-f@%3pY^x4p+cm3L}Fp{MHERLE%px>FV;q|zFO!Ksl<5`2bm$1Hp zslJ%QonnkvSy7R2?PrfGGamH6?LJEJ%WAr%o&4D6Vp8EwD&^1awKPVT z9I|M}-(4C}zXn>RzqL>QI@8s+m82W$%g;}FTFZs%i{3ZL`?QM@9FNUxt9oTQ@+Aea zr{tw6GpYXwB35A0gW2-!Gpvm*x8(E{Ys$fFB}KYTk7ksK%>0O>T4lWrg|8L9HcH*n zTA^e0`Hg|H-&U0fekd9}jMn``#{$s}#A#uNnl-n|@MQ7@jJHx|=zSxz?tOukajZV? zn>d<+ih==3Uu5RW`p7gFx;C|r!L#);H$t!NlBX`*x||aqpje+up-bswbm6vv70q$2 zrSnhZJzx$=2=M9!FXcqa+g59DZ8$}5cG!UaCda<~BN%fS*AdTp-mjv@^GNj5I1GTS z!beWmnNEwibg7Vfw-b&e#%s+A%$`=aTD7F$&m7d}4=8P(lo`hsj`{(;Kk@FRgyaku zf7Y8OWwa{XcFTH@4(spExGVFzbjYy7DQmDX1IiFD^lG}9?QLj-QS!eu3Ng0T+*7bY z>4*FDUZWA)CDni0ugf2t+rd!#uRFblbj9(kFI(cdzgG#6$IpbWh09m?u({S55};o5 zuTHi(rw)2mT9M?n2+(H0#fo&#kUdn<9Mv80qo?GVHIhP*FN(eC;9@!C3`k#|q$hv5 z94tc}hg}?T1|S}p&8LIit8;nq+;^(^$U!!je}a>{q8=?Vc%GoaaQ-S&RE*Kc40gYJ z&H<MDSIgY(GG?0wgezm8iww*LDbT_KWybLkAG>Yb*-pts_Do9Syv5PDBILg zEm3;1Av%rmUxu~#WWNd$R4doI??V^HrB1t6_w-ekcb7cFxaHl%l?$Dr{D zKmqwi(6K>ZuKRxGXa{YQ$8n{e53SUZ&u1}8JnL7DORq>ma9_B1Q~3Ub7hj3Vw}K=@ zg{S~b;gREyqwNoUB`Mb#JSXv|_{&TT-}jY_d{j5M^=eDV#zt_g?|#|R(*$D6z+fxe z1*ih`DJRX;&w`YkpNMD@6~KzojD;Ff=+H8BSvXsa%0#d39X!A|Iv-*q=vyAnBnc@q z#Mm6-i4lJop#3n1`eyo>hdOYp^2!u$AeeFTmM|HeIev<((dq|+#KHijS-ci`*eSed zjX#&4gvbeJIoa^ooarYbO8tp7??;BRE%;9`BYnK4DOzQO(u+}nTSqtNrRDsvWadOf zvQ3?80ZXjy|&o|+n}6}#}2tO0LV9_)qQXi>rgUfOVbHO~b%QD)w|j$-y| zy{`CkMhrz|{#kBXiHV`5KhX&{i=!~T-fPQ_S5Qr>S1 zl>UQe5cn6#1y&PV%sc~y1VpT^)1}+|x{D}0_o?q_-1BV)jrG3j@k@xuTSgN@xRYR0oqQKy;9e_pzzMKl#HdQd*-!W0# zNc$*#BriE!cPgoHhOs7L223|v%x+%IXuD7vkD!c4wFbffTT*hSG#uRS_)pq|y!avDmIk|e(Iu~AY(!Sm7=EISiz1nJq>Dt5Qg%cEP zW^>$MC8!3hn2Bx_z)^Y+GC?`JAZ)?V$1LI7szr05USm0R4Zq4{;fF`<4sHgM|JIf@ z@Ok&fREj|4rvzCzt9s#jcvXF%K2ux<5~l9!6|-c|OGTKxE<@vHFqg)hovlgo$$qYx z4ZiIH!>t~2&T@#}e$E=j&}TAo?M~MGWS8Gjb!^dtHp~+GhW;g@IiABwJJIeD>=y@t z=T*UG_d9XhNj^6GDVagH7r@a{ni4lRtHx!HQlZl{R>nH38|U6s)Bsfh&KicA{d2Fi z2d+{|0A(w{^M1CvXc`ZB&(N%PD^Yvz+V8H}N0*8`WWjAmIWwPgC0`(9^Ck0c;lgC@ z?Bv&ahvpQgkMoI2tGvpIYIO}Oe&u{}or^S`mG5URWuiW8PANV-0OibMY%EGe&Tf}A z#qfQuQFR3~kLBbkIcoccYCb?{%BKFH{-dccN`};b=od#b^o9x0+%&mmr47fHzps;=J**K~O8w>bHw!jaAUdE{FCt)+zgvdmP@4$$M(~pSwJ}(?`fx3O+ecv*O!s@8(7i;MHA7jhr`d(HwqjYWtvd--32Nx?5)JglI(tVWLs_!A)J{ zOw2V8XhU#Y&rXg(+JYAC&X4}SDaUpUasV3{Z@tUcb){%``_kyojw|!mB-C84=P}<1 zbr^yTjx!GG}EC$hq_^8)1eI+bIt8Ym%54tfzFLpf4;Dz-A&!+WIYzwBqZZrEFrZelm;CAcN8 z1W`o&Seg5M;`l#VBo?9*;9x)MYuf**vvFAXYvA9+32Mg$RfPt3N>d8!eyga3kZljz z#lqi zymTVEZzJ_gWkOLcRcbrO5rRh2Mzp%E|2p+uv*^A8`ARgM_)OD*gJ(uX6^&*ekQ9A! zRU{X;tqwOK@|h#$2Frr=rlC^z=p4>4?LUWpQ{5%$tNW0lZWG_86!|CEE9i*k+X*6M z2bC@GtFpsYxSZ}mfh~dg|t~dQT zj$rKUoKSJo>Zg@Ilm_&zz@2g z=sDc$+(DRYj&6UscdXt98sEgX>t!_GG{1kPM@w~*D*3&;BvL#6)iRKE_8*^L|_*Y-zyc5R@@o+T~H5) zw)f}luh~wR@NN`^+kdx!I`qW0Th^^gm|wWd8JYBcUbC*}Na{c1)|$cYK}((f9DL-! z$XInoLeA}?a?n2l8hF5XvhzH?jaDL6yK`*A097mx{hfR%)UE$w@7SgcG_uGUWhSm# z!lQ988Yv7S2St+}GmLXQw+~#-3WCTSAIm7X-ny~rJ)u7sB+a+|PVn0jtn-}zucR>^ zn^JGwavFy)S^VQceSeXw^W0MDVefq&+tStG+v!GD8#kFqeT{M8#3k5RwMS}WM!@Uz zjTIF&Z;T-}KxVF`%^LhbSldSVPU2XR4!FC>7lPMz`Nb>=j=Ys`?^O!+*r#(r%0Z>g z3jE1}?!O|(c*#ws2;1e8meYqt{n}86uhdEu-2K-THyI4FM;vbrkyk7+`U@2xpUqQP zRZuEe8P*XX+bdBhL61tmt7{ndeiMJdTRvogJqD#!-$MQ8!aHEmZCW2-9FB?xLf92c z%#Xyl$ukbxklgIb#5+PI?3#F!>k{8u@un91>Hu7*(fcF?P8AEZFmgdF2)t zYne72@^TX+sH9xrn| z?{qpXzIcBD83s7HZMp$o0i*c@^i79|u-feyxLoyIAMmOGq3G|ntaBs`1S}%5R)`yAN3eq*_rl!9vUsrnXKh(ga88f*N?N-6+UmPV! zr`nZ+)aUJ#gRm^2 zp`j2@Kks0SYXBDFALNm>t|iDMasNv~dg6SrEcdalZeVw;ud5F>R#N;YbCAUdX0BhU IcQ59D0I`JyO#lD@ literal 0 HcmV?d00001 diff --git a/samples/screen-sharing/img/man_head.png b/samples/screen-sharing/img/man_head.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c18b4cf9c35d3fbd8d5b72130b523dada046c3 GIT binary patch literal 20333 zcmXtg1yq#X_ch&}Lx+@zgmibkfPhL$hk&5ONOyOtfOHAcDJ6}R#1PV*LrUim-{tpT zpKIy+>T>3}&%O7Yv(Mi97^(GA86Srl2MGxYUsXln6%rD1G5DE;g#!L$>a^Gb{09{( zr}_p93u}H=a|H>B5lK}+_KiozL8j-IH|Jmb{2fcbr+>6f*Km^!mNh^-W8}<>_>KPL zqpEtZ)r(&B5lD(^>X_=wwG7%m^}hp=&ReO@RPq)ir1*_Y+zR;2X6c3LAMKiR1DAHn zy>gqA^$uH?Z=H+x@=HpaOPfoU#xGC9DJCmT?slIqyEiP~VP`;f-RL@Sx}%r6f*FcJ=^KQdOV_NONZQi&Gr$*k@l=xpG}beoMRh@< z6_{(d25TI22jb%5($dmON=m}Q!odGGH#Y|c25NDjd3RTSX2D(}Mnh)7yT|tLaliF@ zC^M*iHmuj-)mSGH<@)SK?22JNfD>Fa4%MeV5QXa`E=(OeH8s_C+)KmYbJRJO zul%dInIuNM)*=m^EO2mae~HaDn?*H<(KhYfl1cKWJ{KES5F?gf`$O|yh4)${`=7x< zYXgE%IluO7J`A?^UV)4Ug9MmOTu64NBTm)c=~1=tgSb}3z>NBd|&U2 z50_(_Q6f`R)Xp2@uv8~PJJOfMWK~_ukjEpKVC16Tk&h;9=bojcP$Z_EG3S|MRZG6z zR+etQn6sC?LOODjjXQPG!U|tOI)IKsMe;QbXfmn9e=I(K{=BmN{)XYg1 z#9ZHVka2QyS{yT)Arlar+t4QLIVE4j-Ur7;$FmR-go6+<1#bb*@~+6uMONq4m`JiF zV0Ol?;1*wm3nH30%m-Fatqjz^l5`%R%j!3LT=czMbl>@&LP1WR`$_DqinNKD<^bB< z`QoFh|5_CVS_AUsXvQ-n`jU`-l0l5jf%Cf;tNzBuCs8ZcKLCs=@&Pom|Hae zb&rl}X=@X|iK3PCI-9l0UabG{*4^Em7h)CTDM61lBfI^)Qkn_3N+N+zS68?G{bUJ` z|MlSBbS2NTXQ-n5c=E~CL?5}Z|GLuQy6a1zVb6Eq&Wxk~aK>&7H_AbQ-TG}59yFD{Bx4~c|E2sO3WUU`Gx$>;39Q&V0N zS^8Yb36%lUTFFBW#Q_7SN;5m_zp?wFTSEqQwqqqarOD6V)|+((jgOC?oVcjqmzLmA zhbzO31|_))d}He3tFWJBi3r?vWE7W`-EM+?g-btp+S+23s&`1D^%0RYZlBgnoL1GN z6rFWR4~M?M))5S1yt=!8`noW~_hJ^8lG~`tth2^?5Ijf7c}-y;b<-UVZkqd7=IqfqHglDj(VP~UyCJl^1m%DIM{)z96&UHQ3u zXbN`d=6p}sVW!GzAgQM21?2*Ifo}woiOdN+(=V-71fLrNK9g4DhL4Yb*XMuRH=!xL zI6sez>{r{PkTWduVXp3cZ+h9ek8W?Oyt`=#X-R6~FDub972;>gj>|&7@6WVl}e&cUli?U9+r=*G=qA~(v)$_)FOz)xZy?eO7Bb2^9nks)gTu&I7 zLlwdO0HSIOmi++Uh|8$aX{yX%%dQLi5;uf;nDw(I)5J5;yNjk81W6l#bJPW&`@HYV z2W(Nat%vS9{P@1pske@ISAEj=SL^YehJhypwT@~l>f%swjQGCwV-N0M`!xuVOm<5v z-xc;@>kgzo-xKgfz`qb6}GtX`jBK>FY2t_a>P8le}az*vt>@Q zqR!;f(o#5sp9i=qRcsjIt=AluctW)fIrS{LY+>I@(0N*px(s+S+<-=Z4?k!lmugFt+G#Q+n8cs3ws`DwNSz8g~rE zo0UZOu4BBc;2I=le|ft`*?t9fP(4%ZYo=ddaS0A-IRDKOZF9pDzl2R@#;jug^z)02 z(|0hB5kH5;X4liL;n>*N)PqpF_t>G{8>egmm}vM`F(g>WVTSbaHap|r+pl_fD4rV% zfZMJyk<+Q?CanFDpXT~%r3xWSTj0C?{o6qVW>@)k^F7#IsiO{5@O?xb7RNz&+t(in zVDa<$U+}gmrLcLAFugFbiF#9~_jGiA*3_J&FW+um9L#q@6buG#WRrFx`@Im(-o5DN zL#KSjF$C396&r;gKPAi}EnjH*-%Q+HE-x=F5%>$v`zFUs^nCg9Wx@a9j!@z#VEItE zLHVx${wszAvUjsnB!mdRk;;aUL63bm+n4{MtEWZT2%lg=$NFk{?0O0Mke`jL^nje? z0+YNH5Efo1s|eY^NR($$O%t#i|In~_0Zx8l@0{S56m31q;3xRH5u#TrX~Tc31I{}6 zj0O8L;5xf81l^L0mf&HP(BI>z6Ei(MUG|W%F8s6sM{N5zuwt*ZwN+F4?(9jtgv-f> zYBsE>gfk;fg(V0^BX%{LSUeKb*m~eaB%0DS<2&#g>nMB0M%H(>!uZ$6w(G6?dp~^x ze2SOd8yN3AB58x1!$3B3^gB%l@7l33@HyPov3kMfEMZYtDJfhwo)OcsjUi{U3gEDk zHgL(^7hU!I{iRt1WUe5VPBt%2JWr7XWUOL#nwOTBA4*N{cB}j7`HPexmReM(*F?|y z{^RYf8I`IbJqWuFWE(u8acpshXSxds3DtPalT$d(Dnu6FB8QWS899$wR?mQ>aJK-3 z$tFTsNCFjK_GTH=vXe1RWX3Kkv%|tVREvN7xSICAo4&Yk>xF#cK$<{(-ew+M5K)cV zYb-*W#cPgY$weKDffA_9;QI+ak{QFtLD%Q$_~}+zzHO6dy@+U*pqRs_g=f`BKtNyt zRBBNBOsmi+@9ndSl_sBGcQ33{zw5F~s|SSzgwBUQU!H1hB`FpVLL86VswscQi15~Q zWQKe8&^D&XY_*+z=jr?+*m%r~qNpFih~>WE+-DgtDdc%#ReNM0#n8=$myY}2IoqJF zeE1#NrWh>-oHkiKc)Es5IbT~fDa+?%;BwjDbV48-7Csd|8O#cS-!8f@``(>T>*$1v z^U@N$mE;II9y=>zcR;7;ODl3}IU83?7j~5LyA=^%EdAc!tRZr_0B!g%U+=Ukm+=Nw zOvNo*$IbNP`}9b;pdPQ(8A-SO8JOQiEkI$>d01?@98ryHjXm9TmolH$MswxcuZJCT zuu%lWtGtZX>12tvX+;RzmkGaGG*?`npWm%uO8+P;3n|&s=fZf0eqmbURkcnh@}!3A z;byx1ekGW&YAUIUc3dhv5I08l?$6`tA(kKLe-qHy)HLoUUd$rv&sO~Wd43Jgj#tW` zSr4cpI`PcJRElO%^#UmA0q%bv-cuBjW;5~`W>0EH&m%=T zOnu262$1SKmD3jcM}(;FV8pKyO7DsSZ!Txapr?eV-@-2$NiaGb1wNp0lzx3#>dB~V-oY~O-g}GF39IYh;;(G~dry7W zlrb{n`TV_RMy%8VF^?m&w{L0Gn(coOwRUJ#bQhW>{?{cZ2flWIJsS@V1v@NPXPspn zwFsjK;xZyGCKg90-n+&=?F=80a@&Lqh`jtFC84=N)M9QXh19YHnkK|yd;Bxju4FCQ zcboc=WE~my8WUCJFByq!?d{bBSH+fvr}Z=T7UF)p{P54#2qeR73wy>xgyk?>!%BqJ zk%?-;YZ(&)8|kso60>O~&cm+#K3L?WdR~n=`N9uOIi4X>gv+pOAxQO4wSV zB&aXMKh9%ov<8*wLJ36Tw>~odEaQYDTI$R+6w_7${f&<%iv&~AnFJ`EDn_bHHs|8o z=a4bmy8L`plCvWZ%Qx$<^!xt}N>WL-PB_H;^p4>h@nj^LssE7P^cH!Qi%}u;`etP0 zrAIiIR4L;@?bDeZ6*9$sJbEb~5t2xKNp5C-lBg@ZZY24(h6YMJ$-h#ZT0ATprxX$U zLX6%9!_a*%ng#|qU5oRi(bh0sVvRT~Px5^EWuO)J|et!(VX&YO1x15Da^;)yunh*Cnz=ZZ3sSpE9dGGV1Di4cGMFzXC!+uWBh$i19Gw zbBUs*1y!$b@Olt6Xx1G5UTBF@y)z)PgF4OH+T%EQ!PjyoZ)X^y16a%A-6$%IiY^N< z|9b}qT59Sb(Ie3XQFGCRKbqvSY$rIAm$5_J^V%C1-kk&Zp>jGpI$S$aOc`;ESXT=%y--M(6Ordd;?;Ey5UJY%v z63Hr~@?7>P2EQw&I=_7DNTV=Mv2f23#}R}ITS8mylI_j%W^&iUbhn(_vfWJIPJrQq zrqngsyuSHXiW9O#+mejiYx>A)`MFD~3u39>R2J1$rz@}qY*IxmcckOCE{KT2i!bmy zJQ|ZA5He(O*)b-tKRTBZ3r!L}bAJsL6}Y8kPr6nNmY_vvjP1-?~><3hzVxubUm z1{(C!VDG$W$8Wjk?HCdimB%yAaDEbk4}g`O%4`KIFr^()sLb{D#hzOCxk1+$^a(?q`${3d{hxj}B-2NnZ>% zhg97}rE?o@p+yr`S+>A-Hg=7|x>ub#`ph%oCf261B! zhh!jx-slKwUKh-FYve5UDW$;4Uui{D$9+>j>_b^-7mB@P)q*h9rW;*cTv$l-J5bMj z$WU-&nQm+(r@>(`b3dRN4pecXDr-%0`ML*zK#=kYPig*c4r`cYO)t*qUhP8;2P^z+ z_QSdS?=S<25notY1VAR0Imhb`prSjnkXYbdN3LBA-#e`ts_ZtJ{IywJ;q+U;cojj; zJQ8skD}RGP4EOk29XH`=OfW8mvrj+m{MGWRKiEwC7muEZF_)OQIF)4{N77LfiN-|N zDsKzMsj1UBXDiik%%3$8EA6-IkOSzkH6NGOOokI(OhfRBg@8uz&w)rQ9LqaVB}6X` zDk>^hn-68aAf7QMTmph;!J9HR@LVKrtc`1nQ#>sOI+6nLMNWJzp8QBFfF+hAQ!<~yX@Pen0s|DBjI6TE1K`1W)jQv-11~S zAQMAd2wiTPcF}n)BzGW}*-xLCG-K0j{E|wX{SSc?JgmXqC^okJ#3-iFuB2owq-Vb_ zzCX>m$oGP!6Y2LKp00987@vp;mNmEjvdwwUiAoz>!@63s2jPI0#XjD4ECXnv2RUo_u^3+mA9I$FMIYLbq`mhlwUD@@bpsp0v4%2tC-x;{yIkFQ^o zMtlDfh%M^qzo-sY+MmrFE2>FHP1&;D*pV?`tYJ|NGw-n2Sg-%>f6J(vRA}ew>gu#h zWm%==SaFfvmi5KV>XJBwb-THO<3ErIU0nqw;&%e|R7BtfzV*TMBkRbrM2t1Zx2G_9 z7`?-L88RgH^Fta!mdi@^1q~5ezl&KLK@G%?rhf@X)b!p@FNJli8U$7sij@Hcnru>m z8SmA8EsS2oDPX@%PELwPR$rcrqWd2tQN;>0w0Rs#%*3UAf+j?0fpsvqgl=(qB1P?F zYvXCa)moP2YuA!3p17xvEtg2B;`5>$5627NR9D#2>?O(Qi zPIA~acRZgn{_dIUOZHs8@%rAgM-JMH2H*DIRR!bsXrf6nfo=p zr<)lEWoUaPgOGrL01t1}Crd7=rZ;qEJ(x|{35y22MlcV|e5E`4|2}#O09fBQWT7r} zp;jA~nOajRhn(%s1YB?Qjf^rJP)1`{*;X9#&f%xmg)cMTpXc;aeiEMz3hBchf})@q z!a8Hk8l>X;^7n2)i)RDN(m3~he6ZK(7mBsPHwYgopKuEq?p|KJen<6mA;87hS>2w?F{ozkb$)+Jt_XPU3KrEj%X%s*h%)vj>*Buk&ZdtWyUpa?yrZLUTRiyA z%XoMTd@1SU*QhA~1WkS9^EqkAl(DS5&sqs!>0MY?LJDNCoY8sKvq*oA=o-#dSw z_Kw?)mG~2>M=yRi2PY?x8t7|pm}wmo7H#-`*ly`^XFwITId0Z1j*8h-$3ZjU-%Dk~ z%W28URdbz8lWhL*#Ua@0roU#FIOVo=YVP%x7|&_CqM{VmV;c$>q6*`4TxwB_rr2DVAJ&mRU5NWb^6Cy}Tq^yA z%?K@p<6c+)Kj_mWmP|nHO%!QTqRCDOL|txX2%Kp2%@q!%2@c+Bx=RFNqiJrRY!1Ri z*71y|pZ?S_Q$aZV0Ti_p6OBi>y0kDdE_q~Lqu-s^V>SQehl@e`{_zpMH=p#&U4JV` ztZIqCpXrwA*E>*s5%_#T9tP9Jt#YGP;QUWbt<}Uah(Y0)ahh?k#vC$j?4H@T9;oa*P5^J84Xhs<0bb#Cp?atEPV-B? zSUEYcL03L)KnRG)i>Rn*+9X~j>-ZizEh4E+6v7XaI9-e8c^9Laga-9?HEuB9@97zB zwR$ZAPcd>C_*?v%52i+tDBrnxIrMcGFx%PE{%xYKl+Jz8!|Gs#P9reshufd_)4R=a zYPv~_aFLf^lOma0ke;MKAw=Xqv4LD@5EN(8RK+2q8eO_7m&zXTXED&E16b;LM-gb7y;J{MNvgZqVN%f%T z&mo-y4Eht(v$IX+-C?`C?|phLjw1L9IdIDDKD?Enk1r|NI{OD2NDdoo%xzjLRpfo2 z)U5$X`U~qnfJ6c79)a-Vq28K)3OqPHV@^U|4Su7esyg7_c1kB^Dg?54p-c;5bl1mU z2la0qTfg$L4ZbkMCt}Zg${^GSKkdYBUXK%>sx&?Py`B1uDOu(!m5h>d+5h3Lia=I`wh;3x9ni7xMcdPu z@-X?uIvO@Jmi@q4EqOf~!bB2|Vr!nIwVgdVuk^pGpZ|!O-EnyNdEOrL)qB$tKl5)R z6aAAdaRR29#GahWfAZqZZ`K2POos%%medrM>iZr&d-EY^VN`xrO0^004g@x`4@irVWH9aVt>}m zS}U}cuA^Vx)APEa{q6v06rjl!X4u$tMqes}zGm^yxB89xl^Z5*(O#8-IXHgEsguF2 zx3R%)rBJwxI}eVacLpfULXJ!Om%lq&<^R7I0P(GuPmDkF$$$40kdY>HND_;&KiCfw z@8=m67)SerCHn z3YRu&_((@KoR;m90X00WN|4dWHiys%Ip{Zkdio@b!X+cKGXLpo1gKXi6JCRLq{zZCDLqPN`=q13v zvH-7v24M1q&GXV3v3R2yqnFjJ!dX=P+Wo<2gBU})&9UkW`C6f_z9h4q18`SbUU2|e ztM<*dB*^Z3!HqQrbUllGFRma|a*Uhd^#i`mj%s)9DM;-a4#gYpN{>Ab=Xo>wo6*lXETt0rh1G zoKe!bX;IiuuHQ+MI?TC;T#w771p@bb{ELC1-q@5n1_ra%DaO5PU6}rKgp^rO=kkkK z(gFFZlBR+JtAn|p^zrOE`cV*Z+kTEHluDQ=JOhL*CZUM>ulgczohv5Wjxpdy+GNJl zYHDgc?YAd+dCVkjK1dgWETc>_n0=nZX@dH*$|5N9lWkjwUFZMkImL5s9KoLgp=a;# zkUd)%&SJ!#pq|ZV*(*40gbKAAqJ*edQMpNfGYNN_UqX-J`337?e?^4$h~?ue_CB*~ znt+Qt%Xkf#+SAaDV-KmzCRn*q6A7F+uR*P-qu$DN{l$&MNR|J8=kIRrtiDLQ{8#f- z&kXbRQ5Oh)oaW<`KqAxFh@MmWk`g~a=5s%N+PMx}t8h+}@P1iggWKRPhB6Fy#ZO`> zF8QHM;={ZR8t&->xh%mHZr>y_0X%#M%ldGhvw+dq zKx7UubMsDNI+|>biTO(a>vfKh(pRJ+J-Tt@t5`t+7MGKiev`YaM6>b@ zf!+ZxzY*;R7Z*|*e0kSWvTF!XrpZ-iNyKJ?9gx5j%egmv2 z>S+Q4w*-sdEzz=ox2Mvy-LEqU-3+6-+Y#cJaFN+XXWxfacoa!EtQb2YC8gz5ZJqPvw;!KAe=dmE zy>~ty{5v;c5l+uL5S{r&K|2GOd0117x*!t*vDC$Iw90Ak^GUt+b+oBq_6+p$i7k4P zv;v@(UN02CE;4bOwFaQ-NUCgCt||F_S--|*ePVS4bf zK?-YqzGFg1tnMtcqM2-6cVE%G%bB_&i_UEs4vjn5$&E;w>V`7^ZHL`CjihiJH~U<> zs5`S%@+rm8XT#j53~YeJ(H9yViKc=uB3Q@<8VW=G3s%ezT;bv`^xkaeexp!jv!`B{Yq{O&WBD(_45%AOGr; zZx#@Yzd4*y>?D6ASuKkY8Ml8U89K4$Pl9G~Nu3Q2)`cXf7hgGQaJ14pmV9!>Q%Affz+(awg zj9dfRP!Q14VdeqdxCd&KT>1eYj3d;kAP`<*f7Syi$Co__rCHy*t3v~uOt3c&=Q$bw zX=-Z9iPPd-i;|5?rR494*)kaVZ*Fe#@(1<@1qLEh#OGOvqyDesfJ zClBc^?6MP(tyI7J*u}jq;rY*leORyH#>68qD-8zEH&UkP7^9)31s=d;P4eEZd~4c2 zrWIrzU3cKFyJ&6k%2jgw`5j66obF5T&cQ)X5th_YZ|+4C(l|KlS;H?Z1BaJy#p&TL z+oMeHNW@k0ZKk{8t0=^5{$Yj^#**pil+%BCJ=^Z@&p~E!T>RqA)|%1dH7Xm&t>jY> z6cDi5$)QyT^ef=63gc%yqI`b==Ru0)ORs>ULOVkd+WJpg1Y6+w*?u=q^OwGlgxA3~ zWjJsaO-4LfNFtT;b>jz%@dya~P6pUVDe#&fUO%G@2PxHWl#>`uR%cR$Meew*Y)qiM zt--25+RQhb>-~AxmPRb;5yBm ze)BMgDH4SMjCSx@Qwl(5CWkbqLn|+v?>BGS8W=8KZ(W8jis(F41f8E#BPbIW;Upcn zq>99bIO|>xX@%H9NG~fY_L?hzK``BCscbc)&!QUmWl}oLhzc=!E%<=S`IrNbu6-Vb za~xK#s+^FDJ%EVzfRxHM=%S-DxmVdvphK-d(UJchFj_#zaY}HG!(8P8n;2A5LjUM2 z`CrSU4iAtzuL|TE3@4zwxxZ{DoNQ8X>!{nVsHj-J`{ynxDY+$`cn*L3TxiAKsuRTJ z7r=!?Xto9=CA;+>=I@Ug9*(;Sr3+NCERmV9L9%iLHUQAet!TJ-XJUPaF9sLdnf)_Y zR0I6H&(+bR+UR$)1De1Q(bT{iF7~n-l^A*vuOP-R$;rT%R$8iC9z>o%DzW?~&gn*+ z`}+0ulHZKxw^K{IHs;A}93nIb*szNxOdTC-gWhi!fIfVL5h3 zL*S~1E-1Pl9nyf1;<(}f>xNFV~ic{w1P zblAOU=f!)X@%)q9oxnkDYcS{*9gvdk8Rpy>Jy$w{*na^+E&FzZ9b{Rdp*r@-K}-(M zt02(h)=cjiH@R#AZ;Ah~;B*Hz2^tegq=y9kW5FVfhOYs>_04S+q9Hxmz;AgId!)&- z4(8&J=PScPa(wM$t3WI5cYE3Xa3@bFNfzA$s$p;^V=8+ROJZsI+tGi)R0Lb|%GzJ_ zxq$fJg$XR3f1Xul2z{Xbk6wST^}aT;}m%*sKGKT3cT514Eo4wml^fcSrY?E{KlIf<4`pkd4gHSc8-`)9WFnMatJ zA1~GHcXtB-rH&4{V&FjN4yAJa@!p>VTUH z#5g_yffTT>wF4Vo_bTsF2)2Q7nZn=!5*8B8lNY@mtDznY>W8sC7F7mzb~|8kH}3wI z?!ZI0DJqf7NS2CcXKD_P87bYC!LqElc*lDgu{6lj9%F&Y!~5kWy6?5+%o*_pA7eId zD{*daT%lc5o=Vc$s664K_hGA4HX-zP@rCA`9`CF740QH`e3f@NMcuqHGK@}#t(R?} z$4!+CdG(^iwwhfFhu`yLK&k`RV5!=&Z)JsXXx`fSFtQF-k9FKzEiW%G zebq_09pcdc0RcSEagR9I;uz5fuPD@X+>&LtSh+_*tVzPsi0JbV&IjY%v|DWYYYzL%6d`ndrWBcGeV zHCWY8_IG&HFvXzzb;8z@0$(9kis-vXD-!727pJB${62O$XgQ3P?TO9e)}!xo%g&K0 zc1A1#3nGL6?QvV16tHR8fx2wzjLw47(f@Op>gRKo@!*4^6~6$X*W0amgQT%a@wB3k zzOgsDxof5P@U8{eETU1^!i}LfP(c^skm>pH^ z@|`JMgC!;Pb#)TJ<}qLY!QR0Et&)AVy60khu%nvVaeM|AZ|V47M`Y!By3W)RAKt+dFYD*-zRc&dW;s; z`U2F8z&QOFJ(=R}7St&{ul=~V{a0{u$;OS0DT8~ZTC~Wl|1C5pQerS@OW1K>ydHw2mX*d1hAZ4-0W3mi#Rir;eZG`_AsOV z>=~#R#Jhfp04Ct0b%qCz*&1sAh#X{6DT0#CPBf4Q^;DC-^eVdnf4$#>K}_F^1GS#2 z7kZEbwTC)^XN-SMWRK#$rE$umn<^_S%gc9zs$TE7?6=YpXe*G$1^pWu3ZXE<2!Tog zmg>=P+e_mJ4AaPD(%shGYtKjn&ih2CUFmFkzVn2*C0yVm5{*7+&ZTsE9e93zez4sB0Pf!P#%E0c&%C_7o9t9|ZDnz;#nt?vbAx2ijr#m+f`43SJT57%0JOd?uVuWS|W@bh8 zw00&pJ~I*wgVN(+JbU)+k?KdqB=|cyNhjq4sd0#z(qY_3lk$?JhXJujufoU^kUQY6 zxdD*KZN=z&<-bzTY9Xst^qZbgYJoOE*)$G z|5`>cc=MSXB~V6Lxeo+}`@aPM3Ip5P+MGo24hkUU7^*XnGf{{oDVut#i1S)G&=oen z>Q$JudW{#Ta{cpM(LhtXlkLnYv>WBbzeoEe_nBFbAZoD=rs~F^Sz;T0Nots$yMxJ; zw2_&UfE`4H6PlKuzPGyze!cv+`1bW{=te)WK1X-Vb(h6P9f=#tCiWs-%M13G8I6FE zd7O^G1uW9|cQt2K_0#v5wwF(%EZTlDP2=69mO2w4q-K5d(ND{_$k5bk5Kg+^JiUKJ zP76FIiS)VIDLQOJd;|@Hy}dmMNKH+`<^lbHpiXfv=Fs*a9Y%`PO5!s9`(M@fflcBqp2zH=Q0BOc?rfLEYl?Qnm=^duu3~*Oinbo2VXREZBUSsRI6!e$7Wp~#R^0K?Fnv+NOU|?bbBMC?$z>qxwaO98r`ud6rZdw8`^mKK6 zMBMqtID;{0tBAEtsA2p`cuE!j3W>Rb_l(Y+(^HW~-w~1Z7&V^OGeAc*0bE$5{tX3L z7D|QxS7PJ6M|Yd1)M0;4Zg}S$n0*7bAdsxU02grC)&Yofb-WI&W!Oa$xhn7VfSb}8)TsP zWoOE?-xgA1H6({sNFq_j@*!P!opR%x{8*O?| z)uYPHj&)`!SL?^mpX8j{03B30F1K0s#pRB7(kiLwSqpFmF_Or5JAqIH>cvc>^E$Y> zHX|9|j7U+11=EtjJ-XjuT(t~6_8k7YHXzuGv^7nxxSAm>EPOcMaJU{XT~S&p?saAh zqOqexrVBFaLF<-abwm7nTXt2R-D~%6u}!w`g@*9rCqU`}0CQdS&FLoQX@+daOTypc zL2|YX|IyRF_E6|Z7dd!mW|mX__~QVKXe~E5ntu53d}urDP4My|VR-k9602ybU|J5a z=@j$wd=15xY%FJcnc)FA_{C2X8jq7*Kp&5eiW7hP(P)QqtKYoP(Oy!CFsR_oRLdRRWhuCcN*bK5U=&8Rk8kiuTh)nvtp*LqcKvir9LM#AMt+r##jSRV# zZMEv{D3guA2UPMNjPxzFdS4zcKY+S>c6o_g`18+^tK2sd{^1G8aE#%J8#Xg;(q8k? z&nDL#7S)oHu52KLyH4upfpWbAMzlD2cra0U^#u9QbBF}F6IC|!!1R)RWh=PSZfD!t z`+c#e93VTG+sBY%Bxn_f{7ieZC`V*q`I3q4%U$h+27c}2D?`I(+p#>FkB#Pl^m`l; zQ?!JI#dxO_b}kU$CG(Y5RQQ2&ZE%=%0a>w(mcE|)Gb5HwVgfZcC#PYX&-L?$g&HvP z_UJ~mwCRF(&*4EbJ%75o85rxQiM(XRAVE{HjQM<+D5ZshXAb%ERdF^hGaoxRftgYTL4kJAeei65Wz*ON-aA^?3F(G;4*h@~}>SQQI{ zrvpwz@^ax6IP}R|WW|F=PBW?OCb;UPk`vtl*F@cJ!%*v3z;rAL? z$p{3_hGJhbVWM0%=5cI=1>|wO%#r|c?RXC+!Sd{=hx9kuC}()_egqEw{DZG);$xyQ z0VWt!DTM!#(4!}@#5!+k1u^b-M-<|zF#YZ0?FIf2eBp&EU&Mv201gJJ^x|mnY^P{K zLt_8;i@*V|+pFV2?q7Il1PlaSJNU^@mz0TKD)W|;bSCBbb+<*j!N8XIpXD`T6T} z_LV(9!%21#k76U0o7b~9=pz}Tr^Q8C)U>oAs*FtbkS6or2jJhO7OQhRU-vJO6gzO7o zo~#L$tYo}R`N@;fdXd{xWM}x{L;3Qd&9T<^rk|*?L<$r+VJb-iK(GUF&H1$_e*{c_ zEr6i( zn_C@|Q`u)5jG)N|6zTvh9jBDvSW}c(=O!Xc^n1H=($73nL}Q|)wDggBqz)s-2^G-z z{6B8saGUEx+5V8?lO;XFiMqo0ojw;NBtrbhe;1&zaC0!F*5ecd??U?nzbuhK@M{(Y z?GQ6V(8L2_{q31g^#aeYYkGtC4E4s7u6JMrM* zSo{0?%XOCucrFPD`cNTSR%TpAoHG|dJD-(Bf8_;XeayShv zw7!C=;_|l*|0SVC`>7=Tt~D8TiTODUvMm_W9X>t;Bi$;Upc7u2nTc>Sr6A?&pdVs` zMHgll+JX8GhBG~{0?}lNy0u=v1|uQ#y^xt;uC_W)C1Cp3$f9hEhjI_PfXu0 zgca0}qD_Vx*a3KEeTn<9uWnR_oRbW%W}}Yu$}BtqY|k|}6>1L#Hv{ShfbrUIY$r(r z-OQxg0oN6FR@L2|o!JIQ2DB%&^MsF^9wh*-?z+@ZN2LL^Qbyk3S z*n%Cb%mAr=32=^&SExgppEccsA^L4N)Efn8BSOxXpO`ZI`%ZiO@TF@nngBDhgs6%*TL4?RBsJ-vQ|>#;ClCy&y&f z=0q@9J|WhECQF8=O=eD@XliO|Y5AmIJMCa%!-2mZVMbA1u?I}e%JKA@#~*PF9w6Z9 z+Q6G^{%B=2GYg27^{LrkM|qBCXlMu#EM&*Z3kdjJTEb~-I(75;xQ}s+2}{n}nl)Z? zg*7_`1Uph#R>fYfR)n?ALSIekA*!Q+_7?yuvF=qDZ?J+mAPJ6uiH6_-|02SZt{Cuv zj~0m4U`)cKF=ZkJCS*d&`}xU9^qwB{k`EX*zW@*=!U}+;`!2r$3sXm;ByE8e78X|9 zn)ZI@dt~jrRyvS5ii9*zSiw;~%Ybq?zKi+!^Jiz;huLqYc};GPU$V8yzBf0ELGY5A zz!L&xXYCvu9NxeG!wvJE%c$2*bF9|qBEk|76#TXx{boy!NCBDGMo2Dqbl$OjxF+tB zRiKD^41-Lux}o9nXtjH*lBMDCJ)Pd`hH!3rdd7z+*X1X;m1 zZD2jXr4sW8Y*rP@8!_W)L5=xpOJxvB6bNEc6d&x>cJuZtK>v5fuZ$~#+jNb=?9lk0 z$g-@t*=xGO*kFH`|Ci$zHzN)*5ct8x4gfi#X{%JdWzhFeVnIg<6khh>Pxt-4>-c?7 z>^>!3SL)%YoA7u~QMtFj5K_CQgDJl7H zLLfoqjxP6SX(`e*#HP+FEc3_%^OL*Hpn~k<_;?h~RQkP88C_g_hFQ{h_X*g8K z@?IM(^@(M&G?A3?^y5EA08X4bZ1vpBIbA^L8zWZHupK|Y!`+{bjt)TRLM&HMx=oAQ z;=x8IaFWjd{afd{r%QkMi4hB>uHnDhQsSvSvtxDc2+-Hr>*-NO(S79YU6v<8^AWrxH0i|aA@7?$4%iQj03;^grcNV-k`fXr=A1`>#7od*8&R|Sa?*qYz5&XUS zG~)3xnEjOC3=pA!!~muxs$QQKSiHCcCqbkybKu^NFxJ!rmXAjxM%w+8r|Z6r=q*#O zV3yw-cFloBxwWQ7!!>B0RP()eEE$Ws1qAV>$NOue=OG&v* zjyII0g%M>?sRlDiWoMKwgBI&m5nZl&KXb4D9OrjD&-2|r-_Lh(jHCZdQF?++5&bV$ zkDRzQx3Q!>^v`tVYctm@<54*}vHV|zyAw=Z$Ig&BBnL`F0`#@P>7v`vGnZOg%v3*P zKk)lJv6+Ju6!K9?o|Eg3iq_&bvn-=yVrKq$xaDC9O^!wVqF7f}(U!UGZ$}ae?8^aU8_gvDiWZ{Y(D4*>^JH zw7U-4d7!d$1rxMEGzXDfB1z~TJbnwotc z9(LAuycDxx6CR@L4Qbcmo6^$KRJToCC{Fri_p%-S3gmF&G zaZ9Dt1K!w!$Du+)_Ta8qZx%I-v{S9Eh1n)G9{Oc(+oX+}>%QJh>+j@X?)x7eDnv%0 zz3tINjC&(7w^(-Q`fAQx`2=}0oXHV4JW#mtmRwd>Hpra0%X(#wQcV{TbEVg+7P{NE z$vQ)eqG|Vd=0el(_G^|Ye#LU#C3lvkssV3n)Al=B2cs9;W!t6x* zqTM+qh7mFE`7V{@WkyLF-yiNb_`RSeGN_g1R`W4dGBS9lP z%hYo9^jO(ra7atTQ&e4ADr0Teh`zD#;nJGdg{?*_Q=i6-Tp|R0E{S&yzZ&E-Jv~j+ zS0eUWyoPQxTIt^3$kgN86}e$%ldkvM@vZ?))@$;iD}cQ;aU&>=VkKGr+;D{mGAS36#vj8Wc_S+rMQS4noGV}XLonf zkK{0;fZf@;au2^S5;HiH=ajX6($sYNv(KUC6>=ZafgF%Hh(18H>Zd=A+}mq2^|!v` z-W@xn=_e)%ttX?fw~&) zvmKhW%un4fTWk|zWb}?SAF%E!=2Rf%F4{`+Uknv z=G6{n)(Zy*!CL;OTK0wOwTVMVyoHyIK@2k{PvCQ_#XDv46;9w>PPO#&E(%Wt^IPAIp+XohQ6s%Uwn!WXZc0Wk}l)R27_oW zD5|I5@ZN{(5oWiBVmL@NYP)af*0uf6@rFUM*Rx_KDB`j5(_{U(d3l=b50XKmTbs6$ z1Hg3w5u5Rvx+I~G@7O#Z4?7lID$es0VBF8$2mkr%^+)`IpB?A_jSN{XPOTM>bzo??s!DZWKI@}cPBt4x z^T8g5^x()Yy$jf^^99hU5G65OL!pV*IQ%J9P2XzLb44X4UExq3ovpILqNW&$?r2QH z7shHOX=(`iMIsUG%U1`3SFB@-wiJOUu+{oiETPHYmG$-J6s07-10Qj`E{UkYy>UYa zMbE;So=4carRm&#taQz*NeE85P(!|S==10^^lrqsq#i=`K0x7J$L&4v!0#v>rt3dX zj*ZzAc%aLu`#x~u*aw1|Rm-#$-veyw`blrri5P9W? z0VkRS`~89UO*N&{GfyKhdC71;s1b`EKfY!xFwb2LT5xSr?*O%Z*FVRU@ygL?t|%#a zPcINdNucDbTk~40!^T6z`XI%Q8vAG)0TgpQB;+OrrZ?Lr7C(k@*P#lDXIj5%BtIQ| zX$Z0Lx8r}owUf%EdN-0=(YA`g<5ffkg+d7n59eDKjyu5AqFx@6sr=U`tPb$U#tW_i zpL0h20@x(&T(;#w0jvQGm(wHv`1mE{51*B>{|6#STtSj-08s!}DG&(Iq;wlG;%8%{ z&QNj+XA=6C;&&d}!j>rg5`+t<99SX;r1;Iv&!;r`Rf!kEn4orwL(6rTLI`gnJUoOV zQDJ{~cfjDqgY`JrI$E|Y_PdcO6sSdUe^(c4%~c^ytoV|i6&Xek9zMiG>xSs)Xp>g4 z%dn^Q2X7w71UqBNLEmNxc1p;XY8npz^UREgRWM2jM7O?9F6rD~Mg zBIJ}VT0NQ421)G{RV1yJR!|X4`f)zYKQQl{_wKpxo%`N-U+y(GS4SDiUnBtlGAN{- zyAZpDc@!cltV(qhSs{o8Tf0~T(3&qL2oM)?k!W{E8_>j5<_d{gAky6hfCMc7lG6a- z3tPz?0Ak?)eDDVVQ3QZuWa&e+nQ$=XcGA;ch|s&b*0T+j%my7!txdAg(})QC*S=N8Aw<&sT(6aV8&U%&BGsy#MNV(Bf24 zI0>Q4ZZlRyj4Qv5k3Q;iJAL%2ufONy$8-WA1lLTD+W4ss+N}7Peh(F#Bqm7Io;DF^ zr#3Gm@;nwUN%8u=yih;3FEOwEQKgrYQU0cE>K#YeUq0^W0~hilWYUUZe4j^93yy!>@bb=;O*o2vXS1#+){T9UX~3+1|Qn+W5` zL<6I{nUq^b3A{8v*dJkRCSORYmXy^m2oqah(wRjiLsf=2;-+7EyI+$vrUo+bDXTDX z3)zJCcW>7r@dfriaFXntF`9ccj&-jp+CibDqh41+`^2{(eiD5ex*9t;FPVa@%Klui zcGbxSLU(r{G!8m%v}RACF^{vWTEe%>yGjRjN}9H=6^V5iR@sY+TgXW*ck+mG8mvG} z!x;?A?#e*A3cI1YU(6-?{uiMTLsfn6HUL1&b)*;W>jEQEM0+PvL-w!!%uTnBu{+0t5 z*_j)S3v=31b7aV63|L(_Nwj2A4(v+p(f;EPi&QS>QlUvBOs z9oqsfD|cmN)innZax7*(DWJ{aB|DnKTF&*GSjo=6b1o94c)ayjhJErOon$tyHcCg- z!tFG6c$Jz&^LFEusBd~h^bD>Sc3=pjC80!oBT+zKAftnEvEgsn)vXAX-W8TKfA2+w z_#Timr|~eu5_Ljb(2TM7iopiN;1GdPI3WN7xV|wAZUECa@q`;94F5nF=)>S92sqq; zWB>JEf^a-GG${VR1-7&uSD`@Z`vnhtPz)g;3J0vQcnnSr6&?_bbH@c><1f9$eK&Q0 MvUjy>vhh#-2ZE_q@Bjb+ literal 0 HcmV?d00001 diff --git a/samples/screen-sharing/index.html b/samples/screen-sharing/index.html new file mode 100644 index 0000000..dd57f9c --- /dev/null +++ b/samples/screen-sharing/index.html @@ -0,0 +1,66 @@ + + + + + + + WebRTC screen sharing demo + + + + + + + + + + +
+ + +
+
+ +
+
+
+
+
+

+ Choose user +

+
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/samples/screen-sharing/js/client.js b/samples/screen-sharing/js/client.js new file mode 100644 index 0000000..d38ee3f --- /dev/null +++ b/samples/screen-sharing/js/client.js @@ -0,0 +1,209 @@ +var aliceButton = document.getElementById("aliceButton"); +var bobButton = document.getElementById("bobButton"); +var chooseUserForm = document.getElementById("chooseUserForm"); +var helloJumbo = document.getElementById("helloJumbo"); +var helloJumboUser = document.getElementById("helloJumboUser"); +var helloJumboMessage = document.getElementById("helloJumboMessage"); +var helloJumboButton = document.getElementById("helloJumboButton"); +var screenVideo = document.getElementById("screenVideo"); +var extDownloadHref = document.getElementById("extDownloadHref"); + +var screenStream; +var wrtcClient; +var wrtcEventListener = undefined; +var inCall = false; +var currentUser; +aliceButton.onclick = showAlicePage; +bobButton.onclick = showBobPage; + +$(document).ready(function() { + extDownloadHref.href = location.protocol + "//" + location.host + location.pathname + "screen-sharing-extension.crx"; +}); + +function showAlicePage() { + showPageOf("alice"); +} + +function showBobPage() { + showPageOf("bob"); +} + +function showPageOf(user) { + currentUser = user; + connectToRestcomm(); +} + +function fillJumboForSharing() { + helloJumboUser.innerHTML = helloJumboUserHTML(false); + helloJumboMessage.innerHTML = "Now you can share your screen with " + (currentUser == "alice" ? "Bob" : "Alice") + ". Let's go!"; + helloJumboButton.innerHTML = "Share screen"; + helloJumboButton.onclick = startSharing; +} + +function connectToRestcomm() { + var wrtcConfiguration = { + communicationMode: WebRTCommClient.prototype.SIP, + sip: { + sipUserAgent: 'TelScale RTM Olympus/1.0.0', + sipRegisterMode: true, + sipOutboundProxy: 'wss://localhost:5063', // CHANGEME: setup your restcomm instance domain/ip and port + sipDomain: 'localhost', // CHANGEME: setup your restcomm instance domain/ip + sipDisplayName: currentUser, + sipUserName: currentUser, + sipLogin: currentUser, + sipPassword: '1234', + }, + RTCPeerConnection: { + iceServers: undefined, + stunServer: 'stun.l.google.com:19302', + turnServer: undefined, + turnLogin: undefined, + turnPassword: undefined, + } + }; + wrtcEventListener = new WrtcEventListener(); + wrtcClient = new WebRTCommClient(wrtcEventListener); + wrtcClient.open(wrtcConfiguration); + getUserMedia({ + audio: true, + video: true + }, onGetInitialStream, function (error) { + trace("getUserMedia error: ", error); + }); +} + +function onGetInitialStream(stream) { + screenStream = stream; + prepareInitJumbo(); +} + +function prepareInitJumbo() { + chooseUserForm.style.display = 'none'; + helloJumbo.style.display = 'block'; + fillJumboForSharing(); +} + +function startSharing() { + var constraints = { + video: { + mandatory: { + chromeMediaSource: 'desktop', + maxWidth: 1920, + maxHeight: 1080, + maxFrameRate: 10, + minAspectRatio: 1.77, + } + } + }; + getScreenStream(constraints, true, function(stream) { + screenStream = stream; + makeScreenSharingCall(); + }); +} + +// extract to lib +function getScreenStream(constraints, needAudio, onScreenStream) { + var onGotScreenStream = function(screenStream) { + // We need to get audio stream separately to send it with screen stream + // http://stackoverflow.com/a/20063211 + if (needAudio) { + getUserMedia({ + audio: true + }, function (audioStream) { + screenStream.addTrack(audioStream.getAudioTracks()[0]); + onScreenStream(screenStream); + }, function (error) { + trace("getUserMedia Audio Stream error: ", error); + onScreenStream(screenStream); + }); + } else { + onScreenStream(screenStream); + } + } + + var extensionMessageHandler = function (msg) { + var sourceId = msg.data.sourceId; + if (sourceId) { + constraints.video.mandatory.chromeMediaSourceId = sourceId; + getUserMedia(constraints, onGotScreenStream, function (error) { + trace("getUserMedia Screen Stream error: ", error); + }); + } + } + + + window.addEventListener("message", function (msg) { + return extensionMessageHandler(msg) + }); + + window.postMessage("get-sourceId", "*") +} + +function makeScreenSharingCall() { + var calleeContact = (currentUser == "alice" ? "bob" : "alice"); + currentCall = wrtcClient.call(calleeContact, getCallConfiguration()); + inCall = true; + fillSharedJumbo(); +} + +function fillSharedJumbo() { + helloJumboUser.innerHTML = helloJumboUserHTML(true); + helloJumboMessage.innerHTML = "Now " + (currentUser == "alice" ? "Bob" : "Alice") + " can see your screen"; + helloJumboButton.innerHTML = "Stop sharing"; + helloJumboButton.onclick = hangup; +} + +function hangup() { + if (inCall) { + currentCall.close(); + inCall = false; + } +} + +function getCallConfiguration() { + var calleeContact = (currentUser == "alice" ? "bob" : "alice"); + var callConfiguration = { + displayName: calleeContact, + localMediaStream: screenStream, + audioMediaFlag: true, + videoMediaFlag: true, + messageMediaFlag: false, + audioCodecsFilter: '', + videoCodecsFilter: '' + }; + return callConfiguration; +} + +function helloJumboUserHTML(isConnected) { + return "Hello, " + (currentUser == "alice" ? "Alice" : "Bob") + "! Connected" : "label-default'>Disconnected") + "<\/span>"; +} +WrtcEventListener = function () { + trace("WrtcEventListener constructor"); +}; +WrtcEventListener.prototype.onWebRTCommCallRingingEvent = function (webRTCommCall) { + trace("WrtcEventListener::onWebRTCommCallRingingEvent"); + webRTCommCall.accept(getCallConfiguration()); + currentCall = webRTCommCall; +}; +WrtcEventListener.prototype.onWebRTCommCallRingingBackEvent = function (webRTCommCall) { + trace("WrtcEventListener::onWebRTCommCallRingingBackEvent"); + currentCall = webRTCommCall; +}; +WrtcEventListener.prototype.onWebRTCommCallClosedEvent = function (webRTCommCall) { + trace("WrtcEventListener::onWebRTCommCallClosedEvent"); + screenStream.getTracks().forEach(track => track.stop()) + screenVideo.src = undefined; + fillJumboForSharing(); +}; +WrtcEventListener.prototype.onWebRTCommCallOpenedEvent = function (webRTCommCall) { + trace("WrtcEventListener::onWebRTCommCallOpenedEvent: received remote stream"); + screenVideo.src = URL.createObjectURL(webRTCommCall.getRemoteBundledAudioVideoMediaStream() || webRTCommCall.getRemoteVideoMediaStream() || webRTCommCall.getRemoteAudioMediaStream()); +}; +WrtcEventListener.prototype.onWebRTCommCallHangupEvent = function (webRTCommCall) { + trace("WrtcEventListener::onWebRTCommCallHangupEvent"); + currentCall = undefined; +}; + +function trace(text) { + console.log((performance.now() / 1000).toFixed(3) + ": " + text); +} diff --git a/samples/screen-sharing/js/lib/WebRTComm.js b/samples/screen-sharing/js/lib/WebRTComm.js new file mode 100644 index 0000000..22bdda0 --- /dev/null +++ b/samples/screen-sharing/js/lib/WebRTComm.js @@ -0,0 +1,5058 @@ +/** + * @class PrivateJainSipMessageConnector + * @private + * @classdesc Private framework class handling SIP client/user message control + * @constructor + * @param {PrivateJainSipClientConnector} clientConnector clientConnector owner object + * @param {WebRTCommMessage} webRTCommMessage WebRTCommMessage "connected" object + * @param {string} sipCallId SIP Call ID + * @throw {String} Exception "bad argument" + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @author Jean Deruelle (jean.deruelle@ŧelestax.com) + */ +PrivateJainSipMessageConnector = function(clientConnector, webRTCommMessage, sipCallId) { + console.debug("PrivateJainSipMessageConnector:PrivateJainSipMessageConnector()"); + if (clientConnector instanceof PrivateJainSipClientConnector && webRTCommMessage instanceof WebRTCommMessage) { + if (typeof(sipCallId) === 'string') { + this.sipCallId = sipCallId; + } else { + this.sipCallId = new String(new Date().getTime()); + } + this.clientConnector = clientConnector; + this.webRTCommMessage = webRTCommMessage; + this.sipMessageState = undefined; + } else { + throw "PrivateJainSipMessageConnector:PrivateJainSipMessageConnector(): bad arguments" + } +}; + +/** + * SIP Message Control state machine constant + * @private + * @constant + */ +PrivateJainSipMessageConnector.prototype.SIP_MESSAGE_SENDING_STATE = "SIP_MESSAGE_SENDING_STATE"; +PrivateJainSipMessageConnector.prototype.SIP_MESSAGE_407_STATE = "SIP_MESSAGE_407_STATE"; +PrivateJainSipMessageConnector.prototype.SIP_MESSAGE_SENT_STATE = "SIP_MESSAGE_SENT_STATE"; +PrivateJainSipMessageConnector.prototype.SIP_MESSAGE_SEND_FAILED_STATE = "SIP_MESSAGE_SEND_FAILED_STATE"; +PrivateJainSipMessageConnector.prototype.SIP_MESSAGE_RECEIVED_STATE = "SIP_MESSAGE_RECEIVED_STATE"; + + +/** + * Get message id + * @public + * @returns {String} sipCallId + */ +PrivateJainSipMessageConnector.prototype.getId = function() { + return this.sipCallId; +}; + +/** + * Send Authenticated SIP MESSAGE request + * @param {Request} jainSipMessageRequest + * @param {AuthorizationHeader} jainSipAuthorizationHeader + * @private + */ +PrivateJainSipMessageConnector.prototype.sendAuthenticatedSipMessageRequest = function(jainSipMessageRequest, jainSipAuthorizationHeader) { + console.debug("PrivateJainSipMessageConnector:sendAuthenticatedSipMessageRequest()"); + jainSipMessageRequest.removeHeader("Authorization"); + var newJainSipMessageRequest = new SIPRequest(); + newJainSipMessageRequest.setMethod(jainSipMessageRequest.getMethod()); + newJainSipMessageRequest.setRequestURI(jainSipMessageRequest.getRequestURI()); + var headerList = jainSipMessageRequest.getHeaders(); + for (var i = 0; i < headerList.length; i++) { + newJainSipMessageRequest.addHeader(headerList[i]); + } + + var num = new Number(jainSipMessageRequest.getCSeq().getSeqNumber()); + newJainSipMessageRequest.getCSeq().setSeqNumber(num + 1); + newJainSipMessageRequest.setCallId(jainSipMessageRequest.getCallId()); + newJainSipMessageRequest.setVia(this.clientConnector.jainSipListeningPoint.getViaHeader()); + newJainSipMessageRequest.setFrom(jainSipMessageRequest.getFrom()); + newJainSipMessageRequest.setTo(jainSipMessageRequest.getTo()); + newJainSipMessageRequest.setMaxForwards(jainSipMessageRequest.getMaxForwards()); + if (jainSipMessageRequest.getContent() !== null) { + var content = jainSipMessageRequest.getContent(); + var contentType = jainSipMessageRequest.getContentTypeHeader(); + newJainSipMessageRequest.setContent(content, contentType); + } + + this.clientConnector.jainSipMessageFactory.addHeader(newJainSipMessageRequest, jainSipAuthorizationHeader); + jainSipMessageTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(newJainSipMessageRequest); + newJainSipMessageRequest.setTransaction(jainSipMessageTransaction); + jainSipMessageTransaction.sendRequest(); +}; + +/** + * PrivateJainSipClientConnector interface implementation: handle SIP Request event + * @public + * @param {RequestEvent} requestEvent + */ +PrivateJainSipMessageConnector.prototype.onJainSipClientConnectorSipRequestEvent = function(requestEvent) { + console.debug("PrivateJainSipMessageConnector:onJainSipClientConnectorSipRequestEvent() requestEvent : " + requestEvent); + + this.sipMessageState = this.SIP_MESSAGE_RECEIVED_STATE; + + // Send SIP 200 OK response + var jainSipRequest = requestEvent.getRequest(); + var jainSip200OKResponse = jainSipRequest.createResponse(200, "OK"); + jainSip200OKResponse.addHeader(this.clientConnector.jainSipContactHeader); + jainSip200OKResponse.removeHeader("P-Asserted-Identity"); + jainSip200OKResponse.removeHeader("P-Charging-Vector"); + jainSip200OKResponse.removeHeader("P-Charging-Function-Addresses"); + jainSip200OKResponse.removeHeader("P-Called-Party-ID"); + jainSip200OKResponse.removeContent(); + requestEvent.getServerTransaction().sendResponse(jainSip200OKResponse); + + this.webRTCommMessage.from = requestEvent.getRequest().getHeader("From").getAddress().getURI().getUser(); + this.webRTCommMessage.text = requestEvent.getRequest().getContent(); + + if (this.webRTCommMessage.webRTCommCall) { + if (this.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageReceivedEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageReceivedEvent(that.webRTCommMessage); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipRequestEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } else { + // No linked call to the event message, forward the message to the client + if (this.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageReceivedEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageReceivedEvent(that.webRTCommMessage); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipRequestEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + + this.close(); +}; + +/** + * PrivateJainSipClientConnector interface implementation: handle SIP response event + * @public + * @param {ResponseEvent} responseEvent + */ +PrivateJainSipMessageConnector.prototype.onJainSipClientConnectorSipResponseEvent = function(responseEvent) { + console.debug("PrivateJainSipMessageConnector:onJainSipClientConnectorSipResponseEvent() responseEvent : " + responseEvent.getResponse().getStatusLine().getReasonPhrase()); + var jainSipResponse = responseEvent.getResponse(); + var statusCode = parseInt(jainSipResponse.getStatusCode()); + + if (this.sipMessageState === this.SIP_MESSAGE_SENDING_STATE || this.sipMessageState === this.SIP_MESSAGE_407_STATE) { + if (statusCode >= 100 && statusCode < 300) { + this.sipMessageState = this.SIP_MESSAGE_SENT_STATE; + if (this.webRTCommMessage.webRTCommCall) { + if (this.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageSentEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageSentEvent(that.webRTCommMessage); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipResponseEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } else { + // No linked call to the event message, forward the message to the client + if (this.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageSentEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageSentEvent(that.webRTCommMessage); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipResponseEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } else { + if (statusCode === 407) { + this.sipMessageState = this.SIP_MESSAGE_407_STATE; + + // Send Authenticated SIP INVITE + var jainSipOriginalMessageRequest = responseEvent.getOriginalTransaction().getOriginalRequest(); + var jainSipAuthorizationHeader = this.clientConnector.jainSipHeaderFactory.createAuthorizationHeader(jainSipResponse, jainSipOriginalMessageRequest, this.clientConnector.configuration.sipPassword, this.clientConnector.configuration.sipLogin); + this.sendAuthenticatedSipMessageRequest(jainSipOriginalMessageRequest, jainSipAuthorizationHeader); + return; + } else { + this.sipMessageState = this.SIP_MESSAGE_SEND_FAILED_STATE; + } + + if (this.webRTCommMessage.webRTCommCall) { + if (this.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageSendErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageSendErrorEvent(that.webRTCommMessage, jainSipResponse.getStatusLine().getReasonPhrase()); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipResponseEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } else { + if (this.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageSendErrorEvent) + // No linked call to the event message, forward the message to the client + { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageSendErrorEvent(that.webRTCommMessage, jainSipResponse.getStatusLine().getReasonPhrase()); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipResponseEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } + } else { + console.error("PrivateJainSipMessageConnector:onJainSipClientConnectorSipResponseEvent() : bad state : " + this.sipMessageState); + } + this.close(); +}; + +/** + * PrivateJainSipClientConnector interface implementation: handle SIP timeout event + * @public + * @param {TimeoutEvent} timeoutEvent + */ +PrivateJainSipMessageConnector.prototype.onJainSipClientConnectorSipTimeoutEvent = function(timeoutEvent) { + console.debug("PrivateJainSipMessageConnector:onJainSipClientConnectorSipTimeoutEvent()"); + + if (this.sipMessageState === this.SIP_MESSAGE_SENDING_STATE) { + this.sipMessageState = this.SIP_MESSAGE_SEND_FAILED_STATE; + if (this.webRTCommMessage.webRTCommCall) { + if (this.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageSendErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommMessage.webRTCommCall.eventListener.onWebRTCommMessageSendErrorEvent(that.webRTCommMessage, "SIP Timeout"); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipTimeoutEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } else { + // No linked call to the event message, forward the message to the client + if (this.webRTCommMessage.webRTCommClient.eventListener.onWebRTCommMessageSendErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.webRTCommClient.eventListener.onWebRTCommMessageSendErrorEvent(that.webRTCommMessage, "SIP Timeout"); + } catch (exception) { + console.error("PrivateJainSipClientConnector:onJainSipClientConnectorSipTimeoutEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } else { + console.error("PrivateJainSipMessageConnector:onJainSipClientConnectorSipTimeoutEvent() : bad state : " + this.sipMessageState); + } + + this.close(); +}; + +/** + * Asynchronous action : close message connector in each case. + * @public + */ +PrivateJainSipMessageConnector.prototype.close = function() { + console.debug("PrivateJainSipMessageConnector:close(): this.sipCallState=" + this.sipMessageState); + this.clientConnector.removeSessionConnector(this.sipCallId); +}; + + +/** + * Send SIP MESSAGE request + * @public + */ +PrivateJainSipMessageConnector.prototype.send = function() { + console.debug("PrivateJainSipMessageConnector:send()"); + if (this.sipMessageState === undefined) { + var toSipUri = this.webRTCommMessage.to; + if (toSipUri.indexOf("@") === -1) { + //No domain, add caller one + toSipUri += "@" + this.clientConnector.configuration.sipDomain; + } + var fromSipUriString = this.clientConnector.configuration.sipUserName + "@" + this.clientConnector.configuration.sipDomain; + var jainSipCseqHeader = this.clientConnector.jainSipHeaderFactory.createCSeqHeader(1, "MESSAGE"); + var jainSipCallIdHeader = this.clientConnector.jainSipHeaderFactory.createCallIdHeader(this.sipCallId); + var jainSipMaxForwardHeader = this.clientConnector.jainSipHeaderFactory.createMaxForwardsHeader(70); + var jainSipRequestUri = this.clientConnector.jainSipAddressFactory.createSipURI_user_host(null, toSipUri); + var jainSipAllowListHeader = this.clientConnector.jainSipHeaderFactory.createHeaders("Allow: INVITE,ACK,CANCEL,BYE,MESSAGE"); + var jainSipFromUri = this.clientConnector.jainSipAddressFactory.createSipURI_user_host(null, fromSipUriString); + var jainSipFromAdress = this.clientConnector.jainSipAddressFactory.createAddress_name_uri(this.clientConnector.configuration.displayName, jainSipFromUri); + + // Setup display name + if (this.clientConnector.configuration.displayName) { + jainSipFromAdress.setDisplayName(this.clientConnector.configuration.displayName); + } else if (this.clientConnector.configuration.sipDisplayName) { + jainSipFromAdress.setDisplayName(this.clientConnector.configuration.sipDisplayName); + } + var tagFrom = new Date().getTime(); + var jainSipFromHeader = this.clientConnector.jainSipHeaderFactory.createFromHeader(jainSipFromAdress, tagFrom); + var jainSiptoUri = this.clientConnector.jainSipAddressFactory.createSipURI_user_host(null, toSipUri); + var jainSipToAddress = this.clientConnector.jainSipAddressFactory.createAddress_name_uri(null, jainSiptoUri); + var jainSipToHeader = this.clientConnector.jainSipHeaderFactory.createToHeader(jainSipToAddress, null); + var jainSipViaHeader = this.clientConnector.jainSipListeningPoint.getViaHeader(); + var jainSipContentTypeHeader = this.clientConnector.jainSipHeaderFactory.createContentTypeHeader("text", "plain"); + + this.jainSipMessageRequest = this.clientConnector.jainSipMessageFactory.createRequest( + jainSipRequestUri, + "MESSAGE", + jainSipCallIdHeader, + jainSipCseqHeader, + jainSipFromHeader, + jainSipToHeader, + jainSipViaHeader, + jainSipMaxForwardHeader, + jainSipContentTypeHeader, + this.webRTCommMessage.text); + + this.clientConnector.jainSipMessageFactory.addHeader(this.jainSipMessageRequest, jainSipAllowListHeader); + this.clientConnector.jainSipMessageFactory.addHeader(this.jainSipMessageRequest, this.clientConnector.jainSipContactHeader); + var jainSipTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(this.jainSipMessageRequest); + this.jainSipMessageRequest.setTransaction(jainSipTransaction); + jainSipTransaction.sendRequest(); + this.sipMessageState = this.SIP_MESSAGE_SENDING_STATE; + } else { + console.error("PrivateJainSipMessageConnector:send(): bad state, unauthorized action"); + throw "PrivateJainSipMessageConnector:send(): bad state, unauthorized action"; + } +};/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/** + * The JavaScript Framework WebRTComm allow Web Application developers to easily + * integrate multimedia communication service (e.g. VoIP) in their web site, thanks + * to the W3C WebRTC API. The WebRTComm Framework provides a high level communication + * API on top of the opensource JAIN SIP JavaScript Stack (implementing transport of SIP over WebSocket). + * By using a convergent HTTP/SIP Application Server (e.g. Mobicents MSS) or directly access a + * SIP server (e.g. Asterisk), the web developer can rapidly and easily link his web site to a + * telephony infrastructure.
+ * + * A simple test web application of the WebRTComm Framework can be found + * here + * + * @module WebRTComm + * @author Laurent STRULLU (laurent.strullu@orange.com) + */ + +/** + * @class PrivateJainSipCallConnector + * @private + * @classdesc Private framework class handling SIP client/user call control: ringing, ringing back, accept, reject, cancel, bye + * @constructor + * @param {PrivateJainSipClientConnector} clientConnector clientConnector owner object + * @param {WebRTCommCall} webRTCommCall WebRTCommCall "connected" object + * @param {string} sipCallId SIP Call ID + * @throw {String} Exception "bad argument" + * @author Laurent STRULLU (laurent.strullu@orange.com) + */ +PrivateJainSipCallConnector = function(clientConnector, webRTCommCall, sipCallId) { + console.debug("PrivateJainSipCallConnector:PrivateJainSipCallConnector()"); + if (clientConnector instanceof PrivateJainSipClientConnector && webRTCommCall instanceof WebRTCommCall) { + if (typeof(sipCallId) === 'string') { + this.sipCallId = sipCallId; + } else { + this.sipCallId = new String(new Date().getTime()); + } + this.clientConnector = clientConnector; + this.webRTCommCall = webRTCommCall; + this.webRTCommCall.id = this.sipCallId; + this.configuration = undefined; + this.resetSipContext(); + } else { + throw "PrivateJainSipCallConnector:PrivateJainSipCallConnector(): bad arguments" + } +}; + +/** + * SIP Call Control state machine constant + * @private + * @constant + */ +PrivateJainSipCallConnector.prototype.SIP_INVITING_INITIAL_STATE = "INVITING_INITIAL_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_STATE = "INVITING_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_407_STATE = "INVITING_407_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_ACCEPTED_STATE = "INVITING_ACCEPTED_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_LOCAL_HANGINGUP_STATE = "INVITING_LOCAL_HANGINGUP_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_LOCAL_HANGINGUP_407_STATE = "INVITING_LOCAL_HANGINGUP_407_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_CANCELLING_STATE = "INVITING_CANCELLING_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_ERROR_STATE = "INVITING_ERROR_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_HANGUP_STATE = "INVITING_HANGUP_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITING_CANCELLED_STATE = "SIP_INVITING_CANCELLED_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_INITIAL_STATE = "INVITED_INITIAL_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_ACCEPTED_STATE = "INVITED_ACCEPTED_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_LOCAL_HANGINGUP_STATE = "INVITED_LOCAL_HANGINGUP_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_LOCAL_HANGINGUP_407_STATE = "INVITED_LOCAL_HANGINGUP_407_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_HANGUP_STATE = "INVITED_HANGUP_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_ERROR_STATE = "INVITED_ERROR_STATE"; +PrivateJainSipCallConnector.prototype.SIP_INVITED_CANCELLED_STATE = "INVITING_HANGUP_STATE"; + +/** + * Get SIP communication opened/closed status + * @public + * @returns {boolean} true if opened, false if closed + */ +PrivateJainSipCallConnector.prototype.isOpened = function() { + return ((this.sipCallState === this.SIP_INVITING_ACCEPTED_STATE) || (this.sipCallState === this.SIP_INVITED_ACCEPTED_STATE)); +}; + +/** + * Get SIP call ID + * @public + * @returns {string} SIP Call ID + */ +PrivateJainSipCallConnector.prototype.getId = function() { + return this.sipCallId; +}; + +/** + * Open JAIN SIP call/communication, asynchronous action, opened or error event is notified to WebRtcClientCall eventListener + * @public + * @param {object} configuration WebRTC communication configuration + *

Communication configuration sample:
+ * {
+ * displayName:alice,
+ * localMediaStream: [LocalMediaStream],
+ * audioMediaFlag:true,
+ * videoMediaFlag:false,
+ * dataMediaFlag:false,
+ * audioCodecsFilter:PCMA,PCMU,OPUS,
+ * videoCodecsFilter:VP8,H264,
+ * }
+ *

+ * @public + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + */ +PrivateJainSipCallConnector.prototype.open = function(configuration) { + console.debug("PrivateJainSipCallConnector:open()"); + if (this.sipCallState === undefined) { + if (typeof(configuration) === 'object') { + // Calling + if (this.checkConfiguration(configuration) === true) { + this.sipCallState = this.SIP_INVITING_INITIAL_STATE; + this.configuration = configuration; + } else { + console.error("PrivateJainSipCallConnector:open(): bad configuration"); + throw "PrivateJainSipCallConnector:open(): bad configuration"; + } + } else { + // Called + this.sipCallState = this.SIP_INVITED_INITIAL_STATE; + } + } else { + console.error("PrivateJainSipCallConnector:open(): bad state, unauthorized action"); + throw "PrivateJainSipCallConnector:open(): bad state, unauthorized action"; + } +}; + +/** + * Close JAIN SIP communication, asynchronous action, closed event are notified to the WebRTCommClient eventListener + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + */ +PrivateJainSipCallConnector.prototype.close = function() { + console.debug("PrivateJainSipCallConnector:close(): this.sipCallState=" + this.sipCallState); + if (this.sipCallState !== undefined) { + try { + if (this.sipCallState === this.SIP_INITIAL_INVITING_STATE) { + // SIP INVITE has not been sent yet. + this.resetSipContext(); + this.clientConnector.removeSessionConnector(this.sipCallId); + // Notify closed event + this.webRTCommCall.onPrivateCallConnectorCallClosedEvent(); + } else if (this.sipCallState === this.SIP_INVITING_STATE || this.sipCallState === this.SIP_INVITING_407_STATE) { + // SIP INIVTE has been sent, need to cancel it + this.jainSipInvitingCancelRequest = this.jainSipInvitingTransaction.createCancel(); + this.jainSipInvitingCancelRequest.addHeader(this.clientConnector.jainSipContactHeader); + this.jainSipInvitingCancelTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(this.jainSipInvitingCancelRequest); + this.jainSipInvitingCancelTransaction.sendRequest(); + this.sipCallState = this.SIP_INVITING_CANCELLING_STATE; + } else if (this.sipCallState === this.SIP_INVITING_ACCEPTED_STATE) { + // Sent SIP BYE + var jainSipByeRequest = this.jainSipInvitingDialog.createRequest("BYE"); + jainSipByeRequest.removeHeader("Contact"); + jainSipByeRequest.removeHeader("User-Agent"); + jainSipByeRequest.addHeader(this.clientConnector.jainSipContactHeader); + var clientTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(jainSipByeRequest); + this.jainSipInvitingDialog.sendRequest(clientTransaction); + this.sipCallState = this.SIP_INVITING_LOCAL_HANGINGUP_STATE; + // Notify closed event + this.webRTCommCall.onPrivateCallConnectorCallClosedEvent(); + } else if (this.sipCallState === this.SIP_INVITED_INITIAL_STATE) { + // Rejected 480 Temporarily Unavailable + var jainSipResponse480 = this.jainSipInvitedRequest.createResponse(480, "Temporarily Unavailable"); + jainSipResponse480.addHeader(this.clientConnector.jainSipContactHeader); + this.jainSipInvitedTransaction.sendResponse(jainSipResponse480); + this.resetSipContext(); + this.clientConnector.removeSessionConnector(this.sipCallId); + } else if (this.sipCallState === this.SIP_INVITED_ACCEPTED_STATE) { + // Sent SIP BYE + var jainSipByeRequest = this.jainSipInvitedDialog.createRequest("BYE"); + jainSipByeRequest.removeHeader("Contact"); + jainSipByeRequest.removeHeader("User-Agent"); + jainSipByeRequest.addHeader(this.clientConnector.jainSipContactHeader); + var clientTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(jainSipByeRequest); + this.jainSipInvitedDialog.sendRequest(clientTransaction); + this.sipCallState = this.SIP_INVITED_LOCAL_HANGINGUP_STATE; + } else { + this.resetSipContext(); + this.clientConnector.removeSessionConnector(this.sipCallId); + // Notify closed event + this.webRTCommCall.onPrivateCallConnectorCallClosedEvent(); + } + } catch (exception) { + console.error("PrivateJainSipCallConnector:close(): catched exception:" + exception); + this.resetSipContext(); + this.clientConnector.removeSessionConnector(this.sipCallId); + // Notify closed event + this.webRTCommCall.onPrivateCallConnectorCallClosedEvent(); + } + } +}; + +/** + * Process reject of the SIP incoming communication + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + */ +PrivateJainSipCallConnector.prototype.reject = function() { + console.debug("PrivateJainSipCallConnector:reject()"); + if (this.sipCallState === this.SIP_INVITED_INITIAL_STATE) { + try { + // Rejected Temporarily Unavailable + var jainSipResponse486 = this.jainSipInvitedRequest.createResponse(486, "Busy here"); + jainSipResponse486.addHeader(this.clientConnector.jainSipContactHeader); + this.jainSipInvitedTransaction.sendResponse(jainSipResponse486); + } catch (exception) { + console.error("PrivateJainSipCallConnector:reject(): catched exception:" + exception); + } + this.close(); + } else { + console.error("PrivateJainSipCallConnector:reject(): bad state, unauthorized action"); + throw "PrivateJainSipCallConnector:reject(): bad state, unauthorized action"; + } +}; + +/** + * Ignore the incoming SIP communication. This means clearing all local resources without notifying the remote party + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + */ +PrivateJainSipCallConnector.prototype.ignore = function() { + console.debug("PrivateJainSipCallConnector:ignore()"); + if (this.sipCallState === this.SIP_INVITED_INITIAL_STATE) { + try { + // SIP INVITE has not been sent yet. + this.resetSipContext(); + this.clientConnector.removeSessionConnector(this.sipCallId); + // Notify closed event + this.webRTCommCall.onPrivateCallConnectorCallClosedEvent(); + } catch (exception) { + console.error("PrivateJainSipCallConnector:ignore(): catched exception:" + exception); + } + this.close(); + } else { + console.error("PrivateJainSipCallConnector:ignore(): bad state, unauthorized action"); + throw "PrivateJainSipCallConnector:ignore(): bad state, unauthorized action"; + } +}; + +/** + * Check configuration + * @param {object} configuration SIP call configuration JSON object + * @private + * @return true configuration ok false otherwise + */ +PrivateJainSipCallConnector.prototype.checkConfiguration = function(configuration) { + console.debug("PrivateJainSipCallConnector:checkConfiguration()"); + var check = true; + return check; +}; + +/** + * Reset SIP context + * @private + */ +PrivateJainSipCallConnector.prototype.resetSipContext = function() { + console.debug("PrivateJainSipCallConnector:resetSipContext()"); + this.sipCallState = undefined; + this.sdpOffer = undefined; + this.jainSipInvitingSentRequest = undefined; + this.jainSipInvitingDialog = undefined; + this.jainSipInvitingTransaction = undefined; + this.jainSipInvitedReceivedRequest = undefined; + this.jainSipInvitedDialog = undefined; + this.jainSipInvitedTransaction = undefined; +}; + +/** + * Process invitation of outgoing SIP communication + * @public + * @param {String} sdpOffer SDP offer received from RTCPeerConenction + */ +PrivateJainSipCallConnector.prototype.invite = function(sdpOffer) { + console.debug("PrivateJainSipCallConnector:invite()"); + this.sdpOffer = sdpOffer; + this.sendSipInviteRequest(sdpOffer); + this.sipCallState = this.SIP_INVITING_STATE; +}; + + +/** + * Process acceptation of incoming SIP communication + * @public + * @param {string} sdpAnswer SDP answer received from RTCPeerConnection + */ +PrivateJainSipCallConnector.prototype.accept = function(sdpAnswer) { + console.debug("PrivateJainSipCallConnector:accept()"); + // Send 200 OK + var jainSip200OKResponse = this.jainSipInvitedRequest.createResponse(200, "OK"); + jainSip200OKResponse.addHeader(this.clientConnector.jainSipContactHeader); + jainSip200OKResponse.setMessageContent("application", "sdp", sdpAnswer); + this.jainSipInvitedTransaction.sendResponse(jainSip200OKResponse); + this.sipCallState = this.SIP_INVITED_ACCEPTED_STATE; +}; + + +/** + * Send DTMF digit over SIP INFO + * @public + * @param {String} dtmfDigit DTMF digit to send + */ +PrivateJainSipCallConnector.prototype.sendSipDtmf = function(dtmfDigit) { + console.debug("PrivateJainSipCallConnector:sendSipDtmf()"); + + var dialog = null; + if (this.sipCallState === this.SIP_INVITED_ACCEPTED_STATE) { + dialog = this.jainSipInvitedDialog; + } + if (this.sipCallState === this.SIP_INVITING_ACCEPTED_STATE) { + dialog = this.jainSipInvitingDialog; + } + if (dialog) { + try { + var request = dialog.createRequest("INFO"); + request.setContent("Signal=" + dtmfDigit + "\r\nDuration=100\r\n", + this.clientConnector.jainSipHeaderFactory.createContentTypeHeader("application", "dtmf-relay")); + var clientTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(request); + dialog.sendRequest(clientTransaction); + } catch (exception) { + console.error("PrivateJainSipCallConnector:sendSipDtmf(): catched exception exception:" + exception); + } + } + else { + console.error("PrivateJainSipCallConnector:sendSipDtmf(): couldn't retrieve SIP dialog"); + } +}; + + +/** + * PrivateJainSipClientConnector interface implementation: handle SIP Request event + * @public + * @param {RequestEvent} requestEvent + */ +PrivateJainSipCallConnector.prototype.onJainSipClientConnectorSipRequestEvent = function(requestEvent) { + console.debug("PrivateJainSipCallConnector:onJainSipClientConnectorSipRequestEvent()"); + if (this.jainSipInvitingDialog !== undefined) + this.processInvitingSipRequestEvent(requestEvent); + else if (this.jainSipInvitedDialog !== undefined) + this.processInvitedSipRequestEvent(requestEvent); + else { + this.processInvitedSipRequestEvent(requestEvent); + } +}; + +/** + * PrivateJainSipClientConnector interface implementation: handle SIP response event + * @public + * @param {ResponseEvent} responseEvent + */ +PrivateJainSipCallConnector.prototype.onJainSipClientConnectorSipResponseEvent = function(responseEvent) { + console.debug("PrivateJainSipCallConnector:onJainSipClientConnectorSipResponseEvent()"); + if (this.jainSipInvitingDialog !== undefined) + this.processInvitingSipResponseEvent(responseEvent); + else if (this.jainSipInvitedDialog !== undefined) + this.processInvitedSipResponseEvent(responseEvent); + else { + console.warn("PrivateJainSipCallConnector:onJainSipClientConnectorSipResponseEvent(): response ignored"); + } +}; + +/** + * PrivateJainSipClientConnector interface implementation: handle SIP timeout event + * @public + * @param {TimeoutEvent} timeoutEvent + */ +PrivateJainSipCallConnector.prototype.onJainSipClientConnectorSipTimeoutEvent = function(timeoutEvent) { + console.debug("PrivateJainSipCallConnector:onJainSipClientConnectorSipTimeoutEvent()"); + // For the time being force close of the call + this.close(); +}; + + +/** + * Handle SIP request event for inviting call + * @private + * @param {RequestEvent} requestEvent + */ +PrivateJainSipCallConnector.prototype.processInvitingSipRequestEvent = function(requestEvent) { + console.debug("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): this.sipCallState=" + this.sipCallState); + var jainSipRequest = requestEvent.getRequest(); + var requestMethod = jainSipRequest.getMethod(); + if (this.sipCallState === this.SIP_INVITING_INITIAL_STATE) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): bad state, SIP request ignored"); + } else if (this.sipCallState === this.SIP_INVITING_STATE) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): bad state, SIP request ignored"); + } else if (this.sipCallState === this.SIP_INVITING_407_STATE) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): bad state, SIP request ignored"); + } else if (this.sipCallState === this.SIP_INVITING_ERROR_STATE) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): bad state, SIP request ignored"); + } else if (this.sipCallState === this.SIP_INVITING_ACCEPTED_STATE) { + if (requestMethod === "BYE") { + try { + // Sent 200 OK BYE + var jainSip200OKResponse = jainSipRequest.createResponse(200, "OK"); + jainSip200OKResponse.addHeader(this.clientConnector.jainSipContactHeader); + requestEvent.getServerTransaction().sendResponse(jainSip200OKResponse); + // Update SIP call state + this.sipCallState = this.SIP_INVITING_HANGUP_STATE; + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): catched exception exception:" + exception); + } + + // Notify the hangup event + this.webRTCommCall.onPrivateCallConnectorCallHangupEvent(); + + // Close the call + this.close(); + } else { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): bad state, SIP request ignored"); + } + } else { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): bad state, SIP request ignored"); + } +}; + +/** + * Send SIP INVITE request + * @private + */ +PrivateJainSipCallConnector.prototype.sendSipInviteRequest = function() { + console.debug("PrivateJainSipCallConnector:sendSipInviteRequest()"); + // Send INVITE + var calleeSipUri = this.webRTCommCall.getCalleePhoneNumber(); + if (calleeSipUri.indexOf("@") === -1) { + //No domain, add caller one + calleeSipUri += "@" + this.clientConnector.configuration.sipDomain; + } + var fromSipUriString = this.clientConnector.configuration.sipUserName + "@" + this.clientConnector.configuration.sipDomain; + var jainSipCseqHeader = this.clientConnector.jainSipHeaderFactory.createCSeqHeader(1, "INVITE"); + var jainSipCallIdHeader = this.clientConnector.jainSipHeaderFactory.createCallIdHeader(this.sipCallId); + var jainSipMaxForwardHeader = this.clientConnector.jainSipHeaderFactory.createMaxForwardsHeader(70); + var jainSipRequestUri = this.clientConnector.jainSipAddressFactory.createSipURI_user_host(null, calleeSipUri); + var jainSipAllowListHeader = this.clientConnector.jainSipHeaderFactory.createHeaders("Allow: INVITE,ACK,CANCEL,BYE"); + var jainSipFromUri = this.clientConnector.jainSipAddressFactory.createSipURI_user_host(null, fromSipUriString); + var jainSipFromAdress = this.clientConnector.jainSipAddressFactory.createAddress_name_uri(this.configuration.displayName, jainSipFromUri); + // Setup display name + if (this.configuration.displayName) { + jainSipFromAdress.setDisplayName(this.configuration.displayName); + } else if (this.clientConnector.configuration.sipDisplayName) { + jainSipFromAdress.setDisplayName(this.clientConnector.configuration.sipDisplayName); + } + var tagFrom = new Date().getTime(); + var jainSipFromHeader = this.clientConnector.jainSipHeaderFactory.createFromHeader(jainSipFromAdress, tagFrom); + var jainSiptoUri = this.clientConnector.jainSipAddressFactory.createSipURI_user_host(null, calleeSipUri); + var jainSipToAddress = this.clientConnector.jainSipAddressFactory.createAddress_name_uri(null, jainSiptoUri); + var jainSipToHeader = this.clientConnector.jainSipHeaderFactory.createToHeader(jainSipToAddress, null); + var jainSipViaHeader = this.clientConnector.jainSipListeningPoint.getViaHeader(); + var jainSipContentTypeHeader = this.clientConnector.jainSipHeaderFactory.createContentTypeHeader("application", "sdp"); + this.jainSipInvitingRequest = this.clientConnector.jainSipMessageFactory.createRequest(jainSipRequestUri, "INVITE", + jainSipCallIdHeader, + jainSipCseqHeader, + jainSipFromHeader, + jainSipToHeader, + jainSipViaHeader, + jainSipMaxForwardHeader, + jainSipContentTypeHeader, + this.sdpOffer); + + this.clientConnector.jainSipMessageFactory.addHeader(this.jainSipInvitingRequest, jainSipAllowListHeader); + this.clientConnector.jainSipMessageFactory.addHeader(this.jainSipInvitingRequest, this.clientConnector.jainSipContactHeader); + this.jainSipInvitingTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(this.jainSipInvitingRequest); + this.jainSipInvitingRequest.setTransaction(this.jainSipInvitingTransaction); + this.jainSipInvitingDialog = this.jainSipInvitingTransaction.getDialog(); + this.jainSipInvitingTransaction.sendRequest(); +}; + +/** + * Send SIP INVITE request + * @private + * @param {AuthorizationHeader} jainSipAuthorizationHeader Authorization Header + */ +PrivateJainSipCallConnector.prototype.sendAuthenticatedSipInviteRequest = function(jainSipAuthorizationHeader) { + console.debug("PrivateJainSipCallConnector:sendAuthenticatedSipInviteRequest()"); + this.jainSipInvitingRequest.removeHeader("Authorization"); + var newJainSipInvitingRequest = new SIPRequest(); + newJainSipInvitingRequest.setMethod(this.jainSipInvitingRequest.getMethod()); + newJainSipInvitingRequest.setRequestURI(this.jainSipInvitingRequest.getRequestURI()); + var headerList = this.jainSipInvitingRequest.getHeaders(); + for (var i = 0; i < headerList.length; i++) { + newJainSipInvitingRequest.addHeader(headerList[i]); + } + + var num = new Number(this.jainSipInvitingRequest.getCSeq().getSeqNumber()); + newJainSipInvitingRequest.getCSeq().setSeqNumber(num + 1); + newJainSipInvitingRequest.setCallId(this.jainSipInvitingRequest.getCallId()); + newJainSipInvitingRequest.setVia(this.clientConnector.jainSipListeningPoint.getViaHeader()); + newJainSipInvitingRequest.setFrom(this.jainSipInvitingRequest.getFrom()); + newJainSipInvitingRequest.setTo(this.jainSipInvitingRequest.getTo()); + newJainSipInvitingRequest.setMaxForwards(this.jainSipInvitingRequest.getMaxForwards()); + + if (this.jainSipInvitingRequest.getContent() !== null) { + var content = this.jainSipInvitingRequest.getContent(); + var contentType = this.jainSipInvitingRequest.getContentTypeHeader(); + newJainSipInvitingRequest.setContent(content, contentType); + } + this.jainSipInvitingRequest = newJainSipInvitingRequest; + this.clientConnector.jainSipMessageFactory.addHeader(this.jainSipInvitingRequest, jainSipAuthorizationHeader); + this.jainSipInvitingTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(this.jainSipInvitingRequest); + this.jainSipInvitingRequest.setTransaction(this.jainSipInvitingransaction); + this.jainSipInvitingTransaction.sendRequest(); +}; + +/** + * Handle SIP response event for inviting call + * @private + * @param {ResponseEvent} responseEvent + */ +PrivateJainSipCallConnector.prototype.processInvitingSipResponseEvent = function(responseEvent) { + console.debug("PrivateJainSipCallConnector:processInvitingSipResponseEvent(): this.sipCallState=" + this.sipCallState); + var jainSipResponse = responseEvent.getResponse(); + var statusCode = parseInt(jainSipResponse.getStatusCode()); + if (this.sipCallState === this.SIP_INVITING_STATE) { + if (statusCode < 200) { + if (statusCode === 180) { + // Notify the ringing back event + this.webRTCommCall.onPrivateCallConnectorCallRingingBackEvent(); + } else if (statusCode === 183) { + // Notify asynchronously the in progress event + this.webRTCommCall.onPrivateCallConnectorCallInProgressEvent(); + } + console.debug("PrivateJainSipCallConnector:processInvitingSipResponseEvent(): 1XX response ignored"); + } else if (statusCode === 407) { + // Send Authenticated SIP INVITE + var jainSipAuthorizationHeader = this.clientConnector.jainSipHeaderFactory.createAuthorizationHeader(jainSipResponse, this.jainSipInvitingRequest, this.clientConnector.configuration.sipPassword, this.clientConnector.configuration.sipLogin); + this.sendAuthenticatedSipInviteRequest(jainSipAuthorizationHeader); + // Update SIP call state + this.sipCallState = this.SIP_INVITING_407_STATE; + } else if (statusCode === 200) { + this.jainSipInvitingDialog = responseEvent.getOriginalTransaction().getDialog(); + try { + // Send SIP 200 OK ACK + this.jainSipInvitingDialog.setRemoteTarget(jainSipResponse.getHeader("Contact")); + var jainSipMessageACK = this.jainSipInvitingTransaction.createAck(); + jainSipMessageACK.addHeader(this.clientConnector.jainSipContactHeader); + this.jainSipInvitingDialog.sendAck(jainSipMessageACK); + // Update SIP call state + this.sipCallState = this.SIP_INVITING_ACCEPTED_STATE; + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): catched exception, exception:" + exception); + } + + try { + var sdpAnswerString = jainSipResponse.getContent(); + this.webRTCommCall.onPrivateCallConnectorRemoteSdpAnswerEvent(sdpAnswerString); + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): catched exception, exception:" + exception); + + // Notify the error event + this.webRTCommCall.onPrivateCallConnectorCallOpenErrorEvent(exception); + + // Close the call + this.close(); + } + } else { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): SIP INVITE failed:" + jainSipResponse.getStatusCode() + " " + jainSipResponse.getStatusLine().toString()); + // Update SIP call state + this.sipCallState = this.SIP_INVITING_ERROR_STATE; + // Notify asynchronously the error event + this.webRTCommCall.onPrivateCallConnectorCallOpenErrorEvent(jainSipResponse.getStatusLine().getReasonPhrase()); + + this.close(); + } + } else if (this.sipCallState === this.SIP_INVITING_CANCELLING_STATE) { + // Update SIP call state + this.sipCallState = this.SIP_INVITING_CANCELLED_STATE; + this.close(); + } else if (this.sipCallState === this.SIP_INVITING_407_STATE) { + if (statusCode < 200) { + console.debug("PrivateJainSipCallConnector:processInvitingSipResponseEvent(): 1XX response ignored"); + } else if (statusCode === 200) { + this.jainSipInvitingDialog = responseEvent.getOriginalTransaction().getDialog(); + + try { + // Send SIP 200 OK ACK + this.jainSipInvitingDialog.setRemoteTarget(jainSipResponse.getHeader("Contact")); + var jainSipMessageACK = this.jainSipInvitingTransaction.createAck(); + jainSipMessageACK.addHeader(this.clientConnector.jainSipContactHeader); + this.jainSipInvitingDialog.sendAck(jainSipMessageACK); + // Update SIP call state + this.sipCallState = this.SIP_INVITING_ACCEPTED_STATE; + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): catched exception, exception:" + exception); + } + + + try { + var sdpAnswerString = jainSipResponse.getContent(); + this.webRTCommCall.onPrivateCallConnectorRemoteSdpAnswerEvent(sdpAnswerString); + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): catched exception, exception:" + exception); + + // Notify the error event + this.webRTCommCall.onPrivateCallConnectorCallOpenErrorEvent(exception); + + // Close the call + this.close(); + } + } else { + // Update SIP call state + this.sipCallState = this.SIP_INVITING_ERROR_STATE; + + // Notify the error event + this.webRTCommCall.onPrivateCallConnectorCallOpenErrorEvent(jainSipResponse.getStatusLine().getReasonPhrase()); + + // Close the call + this.close(); + } + } else if (this.sipCallState === this.SIP_INVITING_ERROR_STATE) { + console.error("PrivateJainSipCallConnector:processInvitingSipResponseEvent(): bad state, SIP response ignored"); + } else if (this.sipCallState === this.SIP_INVITING_ACCEPTED_STATE) { + console.debug("PrivateJainSipCallConnector:processInvitingSipResponseEvent(): Got reponse status: " + jainSipResponse.getStatusCode()); + } else if (this.sipCallState === this.SIP_INVITING_LOCAL_HANGINGUP_STATE) { + if (statusCode === 407) { + try { + // Send Authenticated BYE request + var jainSipByeRequest = this.jainSipInvitingDialog.createRequest("BYE"); + var clientTransaction = this.clientConnector.jainSipProvider.getNewClientTransaction(jainSipByeRequest); + var jainSipAuthorizationHeader = this.clientConnector.jainSipHeaderFactory.createAuthorizationHeader(jainSipResponse, jainSipByeRequest, this.clientConnector.configuration.sipPassword, this.clientConnector.configuration.sipLogin); + this.clientConnector.jainSipMessageFactory.addHeader(jainSipByeRequest, jainSipAuthorizationHeader); + this.jainSipInvitingDialog.sendRequest(clientTransaction); + // Update SIP call state + this.sipCallState = this.SIP_INVITING_HANGINGUP_407_STATE; + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitingSipRequestEvent(): catched exception, exception:" + exception); + this.close(); + } + } else { + // Force close + this.close(); + } + } else if (this.sipCallState === this.SIP_INVITING_LOCAL_HANGINGUP_407_STATE) { + // Force close + this.close(); + } else { + console.error("PrivateJainSipCallConnector:processInvitingSipResponseEvent(): bad state, SIP response ignored"); + } +}; + +/** + * Handle SIP request event for invited call + * @private + * @param {RequestEvent} requestEvent request event + */ +PrivateJainSipCallConnector.prototype.processInvitedSipRequestEvent = function(requestEvent) { + console.debug("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): this.sipCallState=" + this.sipCallState); + var jainSipRequest = requestEvent.getRequest(); + var requestMethod = jainSipRequest.getMethod(); + var headerFrom = jainSipRequest.getHeader("From"); + if (this.sipCallState === this.SIP_INVITED_INITIAL_STATE) { + if (requestMethod === "INVITE") { + try { + // Store SIP context + this.jainSipInvitedRequest = jainSipRequest; + this.jainSipInvitedTransaction = requestEvent.getServerTransaction(); + this.jainSipInvitedDialog = requestEvent.getServerTransaction().getDialog(); + + // Ringing + var jainSip180ORingingResponse = jainSipRequest.createResponse(180, "Ringing"); + jainSip180ORingingResponse.addHeader(this.clientConnector.jainSipContactHeader); + requestEvent.getServerTransaction().sendResponse(jainSip180ORingingResponse); + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): catched exception, exception:" + exception); + } + + // Notify remote SDP offer to WebRTCommCall + this.webRTCommCall.onPrivateCallConnectorRemoteSdpOfferEvent(this.jainSipInvitedRequest.getContent()); + + // See if there are any custom SIP headers and expose them. Custom headers are headers starting with 'X-' + var customHeaders = {}; + var headerList = jainSipRequest.getHeaders(); + for (var i = 0; i < headerList.length; i++) { + var header = headerList[i]; + if (header.getName().match(/^X-/)) { + customHeaders[header.getName()] = header.getValue(); + } + } + + // Notify incoming communication + var callerPhoneNumber = headerFrom.getAddress().getURI().getUser(); + var callerDisplayName = headerFrom.getAddress().getDisplayName(); + this.webRTCommCall.onPrivateCallConnectorCallRingingEvent(callerPhoneNumber, callerDisplayName, customHeaders); + } else if (requestMethod === "CANCEL") { + try { + // Send 200OK CANCEL + var jainSip200OKResponse = jainSipRequest.createResponse(200, "OK"); + jainSip200OKResponse.addHeader(this.clientConnector.jainSipContactHeader); + requestEvent.getServerTransaction().sendResponse(jainSip200OKResponse); + + // Send 487 (Request Cancelled) for the INVITE + var jainSipResponse487 = this.jainSipInvitedRequest.createResponse(487, "(Request Cancelled)"); + this.jainSipInvitedTransaction.sendMessage(jainSipResponse487); + + // Update SIP call state + this.sipCallState = this.SIP_INVITED_CANCELLED_STATE; + this.webRTCommCall.onPrivateCallConnectorCallCanceledEvent(); + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): catched exception, exception:" + exception); + } + + // Notify asynchronously the hangup event + this.webRTCommCall.onPrivateCallConnectorCallHangupEvent(); + + // Close the call + this.close(); + } else { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): bad state, SIP request ignored"); + } + } else if (this.sipCallState === this.SIP_INVITED_ACCEPTED_STATE) { + if (requestMethod === "BYE") { + try { + // Send 200OK + var jainSip200OKResponse = jainSipRequest.createResponse(200, "OK"); + jainSip200OKResponse.addHeader(this.clientConnector.jainSipContactHeader); + requestEvent.getServerTransaction().sendResponse(jainSip200OKResponse); + + // Update SIP call state + this.sipCallState = this.SIP_INVITED_HANGUP_STATE; + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): catched exception exception:" + exception); + } + + // Notify asynchronously the hangup event + this.webRTCommCall.onPrivateCallConnectorCallHangupEvent(); + + // Close the call + this.close(); + } else if (requestMethod === "ACK") { + this.jainSipInvitedDialog = requestEvent.getServerTransaction().getDialog(); + } else { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): bad state, SIP request ignored"); + } + } else if (this.sipCallState === this.SIP_INVITED_LOCAL_HANGINGUP_STATE) { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): bad state, SIP request ignored"); + } else if (this.sipCallState === this.SIP_INVITED_LOCAL_HANGINGUP_407_STATE) { + console.error("PrivateJainSipCallConnector:processInvitedSipRequestEvent(): bad state, SIP request ignored"); + } +}; + +/** + * Handle SIP response event for invited call + * @private + * @param {ResponseEvent} responseEvent response event + */ +PrivateJainSipCallConnector.prototype.processInvitedSipResponseEvent = function(responseEvent) { + console.debug("PrivateJainSipCallConnector:processInvitedSipResponseEvent(): this.invitingState=" + this.invitingState); + var jainSipResponse = responseEvent.getResponse(); + var statusCode = parseInt(jainSipResponse.getStatusCode()); + if (this.sipCallState === this.SIP_INVITED_STATE) { + console.error("PrivateJainSipCallConnector:processInvitedSipResponseEvent(): bad state, SIP response ignored"); + } else if (this.sipCallState === this.SIP_INVITED_ACCEPTED_STATE) { + console.debug("PrivateJainSipCallConnector:processInvitedSipResponseEvent(): Got reponse status: " + jainSipResponse.getStatusCode()); + } else if (this.sipCallState === this.SIP_INVITED_LOCAL_HANGINGUP_STATE) { + if (statusCode === 407) { + try { + // Send Authenticated BYE request + var jainSipByeRequest = this.jainSipInvitedDialog.createRequest("BYE"); + var clientTransaction = this.jainSipProvider.getNewClientTransaction(jainSipByeRequest); + var jainSipAuthorizationHeader = this.jainSipHeaderFactory.createAuthorizationHeader(jainSipResponse, jainSipByeRequest, this.configuration.sipPassword, this.configuration.sipLogin); + this.jainSipMessageFactory.addHeader(jainSipByeRequest, jainSipAuthorizationHeader); + jainSipByeRequest.addHeader(this.clientConnector.jainSipContactHeader); + this.jainSipInvitedDialog.sendRequest(clientTransaction); + + // Update SIP call state + this.sipCallState = this.SIP_INVITED_HANGINGUP_407_STATE; + } catch (exception) { + console.error("PrivateJainSipCallConnector:processInvitedSipResponseEvent(): catched exception, exception:" + exception); + this.close(); + } + } else { + this.close(); + } + } else if (this.sipCallState === this.SIP_INVITED_LOCAL_HANGINGUP_407_STATE) { + // Force close + this.close(); + } else { + console.error("PrivateJainSipCallConnector:processInvitedSipResponseEvent(): bad state, SIP request ignored"); + } +}; +/** + * @class PrivateJainSipClientConnector + * @classdesc Private framework class handling SIP client/user agent control + * @constructor + * @private + * @param {WebRTCommClient} webRTCommClient "connected" WebRTCommClient object + * @throw {String} Exception "bad argument" + * @author Laurent STRULLU (laurent.strullu@orange.com) + */ +PrivateJainSipClientConnector = function(webRTCommClient) { + console.debug("PrivateJainSipClientConnector:PrivateJainSipClientConnector()"); + if (webRTCommClient instanceof WebRTCommClient) { + this.webRTCommClient = webRTCommClient; + this.reset(); + } else { + throw "PrivateJainSipClientConnector:PrivateJainSipClientConnector(): bad arguments" + } +}; + +// Private webRtc class variable +PrivateJainSipClientConnector.prototype.SIP_ALLOW_HEADER = "Allow: INVITE,ACK,CANCEL,BYE,OPTIONS,MESSAGE"; + +// State of SIP REGISTER state machine +PrivateJainSipClientConnector.prototype.SIP_UNREGISTERED_STATE = "SIP_UNREGISTERED_STATE"; +PrivateJainSipClientConnector.prototype.SIP_REGISTERING_STATE = "SIP_REGISTERING_STATE"; +PrivateJainSipClientConnector.prototype.SIP_REGISTER_REFRESHING_STATE = "SIP_REGISTER_REFRESHING_STATE"; +PrivateJainSipClientConnector.prototype.SIP_REGISTERING_401_STATE = "SIP_REGISTERING_401_STATE"; +PrivateJainSipClientConnector.prototype.SIP_REGISTERED_STATE = "SIP_REGISTERED_STATE"; +PrivateJainSipClientConnector.prototype.SIP_UNREGISTERING_401_STATE = "SIP_UNREGISTERING_401_STATE"; +PrivateJainSipClientConnector.prototype.SIP_UNREGISTERING_STATE = "SIP_UNREGISTERING_STATE"; +PrivateJainSipClientConnector.prototype.SIP_SESSION_EXPIRATION_TIMER = 60; +PrivateJainSipClientConnector.prototype.SIP_REGISTER_REFRESH_TIMER = 50; + +/** + * Get SIP client/user agent opened/closed status + * @public + * @returns {boolean} true if opened, false if closed + */ +PrivateJainSipClientConnector.prototype.isOpened = function() { + return this.openedFlag; +}; + + +/** + * Open SIP client/user agent, asynchronous action, opened or error event is notified to WebRtcClientComm + * @public + * @param {object} configuration SIP client/user agent configuration
+ *

Client configuration sample:
+ * {
+ * sipUriContactParameters:undefined,
+ * sipUserAgent:"WebRtcCommTestWebApp/0.0.1",
+ * sipUserAgentCapabilities:"+g.oma.sip-im",
+ * sipOutboundProxy:"ws://localhost:5082",
+ * sipDomain:"sip.net",
+ * sipUserName:"alice",
+ * sipLogin:"alice@sip.net,
+ * sipPassword:"1234567890",
+ * sipRegisterMode:true,
+ * }
+ *

+ * @throw {String} Exception "bad argument" + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception [internal error] + */ +PrivateJainSipClientConnector.prototype.open = function(configuration) { + console.debug("PrivateJainSipClientConnector:open()"); + try { + if (typeof(configuration) === 'object') { + if (this.openedFlag === false) { + if (this.checkConfiguration(configuration) === true) { + this.configuration = configuration; + + // Create JAIN SIP main objects + this.jainSipFactory = new SipFactory(); + this.jainSipStack = this.jainSipFactory.createSipStack(this.configuration.sipUserAgent); + this.jainSipListeningPoint = this.jainSipStack.createListeningPoint(this.configuration.sipOutboundProxy); + this.jainSipProvider = this.jainSipStack.createSipProvider(this.jainSipListeningPoint); + this.jainSipProvider.addSipListener(this); + this.jainSipHeaderFactory = this.jainSipFactory.createHeaderFactory(); + this.jainSipAddressFactory = this.jainSipFactory.createAddressFactory(); + this.jainSipMessageFactory = this.jainSipFactory.createMessageFactory(); + this.jainSipContactHeader = this.jainSipListeningPoint.createContactHeader(this.configuration.sipUserName); + if (this.configuration.sipUserAgentCapabilities) { + this.jainSipContactHeader.setParameter(this.configuration.sipUserAgentCapabilities, null); + } + if (this.configuration.sipUriContactParameters) { + try { + var sipUri = this.jainSipContactHeader.getAddress().getURI(); + var parameters = this.configuration.sipUriContactParameters.split(";"); + for (var i = 0; i < parameters.length; i++) { + var nameValue = parameters[i].split("="); + sipUri.uriParms.set_nv(new NameValue(nameValue[0], nameValue[1])); + } + } catch (exception) { + console.error("PrivateJainSipClientConnector:open(): catched exception:" + exception); + } + } + + this.jainSipMessageFactory.setDefaultUserAgentHeader(this.jainSipHeaderFactory.createUserAgentHeader(this.jainSipStack.getUserAgent())); + this.jainSipStack.start(); + } else { + console.error("PrivateJainSipClientConnector:open(): bad configuration"); + throw "PrivateJainSipClientConnector:open(): bad configuration"; + } + } else { + console.error("PrivateJainSipClientConnector:open(): bad state, unauthorized action"); + throw "PrivateJainSipClientConnector:open(): bad state, unauthorized action"; + } + } else { + console.error("PrivateJainSipClientConnector:open(): bad argument, check API documentation"); + throw "PrivateJainSipClientConnector:open(): bad argument, check API documentation" + } + } catch (exception) { + this.reset(); + console.error("PrivateJainSipClientConnector:open(): catched exception:" + exception); + throw exception; + } +}; + +/** + * Close SIP client/User Agent, asynchronous action,closed event is notified to WebRtcClientComm + * Open SIP Call/communication are closed + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception [internal error] + */ +PrivateJainSipClientConnector.prototype.close = function() { + console.debug("PrivateJainSipClientConnector:close()"); + try { + if (this.openedFlag === true) { + //Force close of open SIP communication + for (var sipSessionId in this.sessionConnectors) { + + var sessionConnector = this.sessionConnectors[sipSessionId]; + if (sessionConnector instanceof PrivateJainSipCallConnector) { + if (sessionConnector.isOpened()) { + sessionConnector.close(); + } + } + } + if (this.configuration.sipRegisterMode === true) { + if (this.sipRegisterState === this.SIP_REGISTERED_STATE) { + this.sipUnregisterPendingFlag = false; + this.sipRegisterState = this.SIP_UNREGISTERING_STATE; + if (this.sipRegisterRefreshTimer) { + // Cancel SIP REGISTER refresh timer + clearTimeout(this.sipRegisterRefreshTimer); + } + this.sendNewSipRegisterRequest(0); + } else { + // Refresh SIP REGISTER ongoing, wait the end and excute SIP unregistration + this.sipUnregisterPendingFlag = true; + } + } else { + this.reset(); + this.webRTCommClient.onPrivateClientConnectorClosedEvent(); + } + } else { + console.error("PrivateJainSipClientConnector:close(): bad state, unauthorized action"); + throw "PrivateJainSipClientConnector:close(): bad state, unauthorized action"; + } + } catch (exception) { + console.error("PrivateJainSipClientConnector:close(): catched exception:" + exception); + throw exception; + } +}; + + +/** + * Create new CallConnector object + * @public + * @param {WebRTCommCall|WebRTCommMessage} webRTCommSession connected "object" + * @param {string} sipSessionId SIP CALL ID + * @throw {String} Exception "bad argument, check API documentation" + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception [internal error] + */ +PrivateJainSipClientConnector.prototype.createPrivateSessionConnector = function(webRTCommSession, sipSessionId) { + console.debug("PrivateJainSipClientConnector:createPrivateSessionConnector()"); + try { + if (this.openedFlag === true) { + if (webRTCommSession instanceof WebRTCommCall) { + var sessionConnector = new PrivateJainSipCallConnector(this, webRTCommSession, sipSessionId); + console.debug("PrivateJainSipClientConnector:createPrivateSessionConnector():sessionConnector.sipCallId=" + sessionConnector.sipCallId); + this.sessionConnectors[sessionConnector.sipCallId] = sessionConnector; + return sessionConnector; + + } else if (webRTCommSession instanceof WebRTCommMessage) { + var sessionConnector = new PrivateJainSipMessageConnector(this, webRTCommSession, sipSessionId); + console.debug("PrivateJainSipClientConnector:createPrivateSessionConnector():sessionConnector.sipCallId=" + sessionConnector.sipCallId); + this.sessionConnectors[sessionConnector.sipCallId] = sessionConnector; + return sessionConnector; + } else { + console.error("PrivateJainSipClientConnector:createPrivateSessionConnector(): bad argument, check API documentation"); + throw "PrivateJainSipClientConnector:createPrivateSessionConnector(): bad argument, check API documentation" + } + } + console.error("PrivateJainSipClientConnector:createPrivateSessionConnector(): bad state, unauthorized action"); + throw "PrivateJainSipClientConnector:createPrivateSessionConnector(): bad state, unauthorized action"; + } catch (exception) { + console.error("PrivateJainSipClientConnector:createPrivateSessionConnector(): catched exception:" + exception); + throw exception; + } +}; + + +/** + * Remove a PrivateJainSipClientConnector object in the call table + * @private + * @param {string} sipSessionId SIP CALL ID + */ +PrivateJainSipClientConnector.prototype.removeSessionConnector = function(sipSessionId) { + console.debug("PrivateJainSipClientConnector:removeSessionConnector(): sipSessionId=" + sipSessionId); + delete this.sessionConnectors[sipSessionId]; +}; + +/** + * Reset client context + * @private + */ +PrivateJainSipClientConnector.prototype.reset = function() { + console.debug("PrivateJainSipClientConnector:reset()"); + this.openedFlag = false; + this.configuration = undefined; + this.resetSipRegisterContext(); + this.sessionConnectors = {}; +}; + +/** + * Reset SIP register context + * @private + */ +PrivateJainSipClientConnector.prototype.resetSipRegisterContext = function() { + console.debug("PrivateJainSipClientConnector:resetSipRegisterContext()"); + if (this.sipRegisterRefreshTimer !== undefined) + clearTimeout(this.sipRegisterRefreshTimer); + this.sipRegisterState = this.SIP_UNREGISTERED_STATE; + this.sipRegisterRefreshTimer = undefined; + this.sipRegisterAuthenticatedFlag = false; + this.jainSipRegisterRequest = undefined; + this.jainSipRegisterTransaction = undefined; + this.jainSipRegisterDialog = undefined; + this.sipUnregisterPendingFlag = false; +}; + +/** + * Check configuration + * @private + * @param {object} configuration SIP user agent configuration + * *

Client configuration sample:
+ * {
+ * sipUserAgent:"WebRtcCommTestWebApp/0.0.1",
+ * sipUserAgentCapabilities:"+g.oma.sip-im",
+ * sipOutboundProxy:"ws://localhost:5082",
+ * sipDomain:"sip.net",
+ * sipUserName:"alice",
+ * sipLogin:"alice@sip.net,
+ * sipPassword:"1234567890",
+ * sipUserAgentCapabilities,
+ * sipRegisterMode:true,
+ * }
+ *

+ * @return true configuration ok false otherwise + */ +PrivateJainSipClientConnector.prototype.checkConfiguration = function(configuration) { + console.debug("PrivateJainSipClientConnector:checkConfiguration()"); + try { + var check = true; + // sipLogin, sipPassword, sipUserAgentCapabilities not mandatory + if (configuration.sipUserAgent === undefined || configuration.sipUserAgent.length === 0) { + check = false; + console.error("PrivateJainSipClientConnector:checkConfiguration(): missing configuration parameter sipUserAgent"); + } + + // stunServer, sipLogin, sipPassword, sipApplicationprofile not mandatory + if (configuration.sipOutboundProxy === undefined || configuration.sipOutboundProxy.length === 0) { + check = false; + console.error("PrivateJainSipClientConnector:checkConfiguration(): missing configuration parameter sipOutboundProxy"); + } + + if (configuration.sipDomain === undefined || configuration.sipDomain.length === 0) { + check = false; + console.error("PrivateJainSipClientConnector:checkConfiguration(): missing configuration parameter sipDomain"); + } + + if (configuration.sipUserName === undefined || configuration.sipUserName.length === 0) { + check = false; + console.error("PrivateJainSipClientConnector:checkConfiguration(): missing configuration parameter sipUserName"); + } + + if (configuration.sipRegisterMode === undefined || configuration.sipRegisterMode.length === 0) { + check = false; + console.error("PrivateJainSipClientConnector:checkConfiguration(): missing configuration parameter sipRegisterMode"); + } + + if (configuration.sipLogin !== undefined && configuration.sipLogin === "") { + configuration.sipLogin = undefined; + } + + if (configuration.sipPassword !== undefined && configuration.sipPassword === "") { + configuration.sipPassword = undefined; + } + + if (configuration.sipUserAgentCapabilities !== undefined && configuration.sipUserAgentCapabilities === "") { + configuration.sipUserAgentCapabilities = undefined; + } + + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipUserAgent:" + configuration.sipUserAgent); + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipUserAgentCapabilities:" + configuration.sipUserAgentCapabilities); + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipOutboundProxy:" + configuration.sipOutboundProxy); + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipDomain:" + configuration.sipDomain); + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipUserName:" + configuration.sipUserName); + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipLogin:" + configuration.sipLogin); + console.debug("PrivateJainSipClientConnector:checkConfiguration(): configuration.sipRegisterMode:" + configuration.sipRegisterMode); + return check; + } catch (exception) { + console.error("PrivateJainSipClientConnector:checkConfiguration(): catched exception:" + exception); + return false; + } +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process WebSocket connection event + * @public + */ +PrivateJainSipClientConnector.prototype.processConnected = function() { + console.debug("PrivateJainSipClientConnector:processConnected()"); + try { + // Start SIP REGISTER process + if (this.openedFlag === false) { + if (this.configuration.sipRegisterMode === true) { + this.resetSipRegisterContext(); + // Send SIP REGISTER request + this.sendNewSipRegisterRequest(this.SIP_SESSION_EXPIRATION_TIMER); + this.sipRegisterState = this.SIP_REGISTERING_STATE; + return; + } else { + this.openedFlag = true; + this.webRTCommClient.onPrivateClientConnectorOpenedEvent(); + return; + } + } else { + console.error("PrivateJainSipClientConnector:processConnected(): this.openedFlag==true !"); + } + + // Open failed + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent(); + } catch (exception) { + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent(); + console.error("PrivateJainSipClientConnector:processConnected(): catched exception:" + exception); + } +}; + + +/** + * Send SIP REGISTER request + * @param {int} expiration + * @private + */ +PrivateJainSipClientConnector.prototype.sendNewSipRegisterRequest = function(expiration) { + console.debug("PrivateJainSipClientConnector:sendNewSipRegisterRequest()"); + var fromSipUriString = this.configuration.sipUserName + "@" + this.configuration.sipDomain; + var jainSipCseqHeader = this.jainSipHeaderFactory.createCSeqHeader(1, "REGISTER"); + var jainSipCallIdHeader = this.jainSipHeaderFactory.createCallIdHeader(new String(new Date().getTime())); + var jainSipExpiresHeader = this.jainSipHeaderFactory.createExpiresHeader(expiration); + var jainSipMaxForwardHeader = this.jainSipHeaderFactory.createMaxForwardsHeader(70); + var jainSipRequestUri = this.jainSipAddressFactory.createSipURI_user_host(null, this.configuration.sipDomain); + var jainSipAllowListHeader = this.jainSipHeaderFactory.createHeaders(PrivateJainSipClientConnector.prototype.SIP_ALLOW_HEADER); + var jainSipFromUri = this.jainSipAddressFactory.createSipURI_user_host(null, fromSipUriString); + var jainSipFromAddress = this.jainSipAddressFactory.createAddress_name_uri(null, jainSipFromUri); + var random = new Date(); + var tag = random.getTime(); + var jainSipFromHeader = this.jainSipHeaderFactory.createFromHeader(jainSipFromAddress, tag); + var jainSipToHeader = this.jainSipHeaderFactory.createToHeader(jainSipFromAddress, null); + var jainSipViaHeader = this.jainSipListeningPoint.getViaHeader(); + this.jainSipRegisterRequest = this.jainSipMessageFactory.createRequest(jainSipRequestUri, "REGISTER", jainSipCallIdHeader, jainSipCseqHeader, jainSipFromHeader, jainSipToHeader, jainSipViaHeader, jainSipMaxForwardHeader); + this.jainSipMessageFactory.addHeader(this.jainSipRegisterRequest, jainSipExpiresHeader); + this.jainSipMessageFactory.addHeader(this.jainSipRegisterRequest, jainSipAllowListHeader); + this.jainSipMessageFactory.addHeader(this.jainSipRegisterRequest, this.jainSipContactHeader); + + this.jainSipRegisterTransaction = this.jainSipProvider.getNewClientTransaction(this.jainSipRegisterRequest); + this.jainSipRegisterDialog = this.jainSipRegisterTransaction.getDialog(); + this.jainSipRegisterRequest.setTransaction(this.jainSipRegisterTransaction); + this.jainSipRegisterTransaction.sendRequest(); +}; + +/** + * Send Authentitated SIP REGISTER request + * @param {AuthorizationHeader} jainSipAuthorizationHeader + * @private + */ +PrivateJainSipClientConnector.prototype.sendAuthenticatedSipRegisterRequest = function(jainSipAuthorizationHeader) { + console.debug("PrivateJainSipClientConnector:sendAuthenticatedSipRegisterRequest()"); + this.jainSipRegisterRequest.removeHeader("Authorization"); + var newJainSipRegisterRequest = new SIPRequest(); + newJainSipRegisterRequest.setMethod(this.jainSipRegisterRequest.getMethod()); + newJainSipRegisterRequest.setRequestURI(this.jainSipRegisterRequest.getRequestURI()); + var headerList = this.jainSipRegisterRequest.getHeaders(); + for (var i = 0; i < headerList.length; i++) { + newJainSipRegisterRequest.addHeader(headerList[i]); + } + + var num = new Number(this.jainSipRegisterRequest.getCSeq().getSeqNumber()); + newJainSipRegisterRequest.getCSeq().setSeqNumber(num + 1); + newJainSipRegisterRequest.setCallId(this.jainSipRegisterRequest.getCallId()); + newJainSipRegisterRequest.setVia(this.jainSipListeningPoint.getViaHeader()); + newJainSipRegisterRequest.setFrom(this.jainSipRegisterRequest.getFrom()); + newJainSipRegisterRequest.setTo(this.jainSipRegisterRequest.getTo()); + newJainSipRegisterRequest.setMaxForwards(this.jainSipRegisterRequest.getMaxForwards()); + + this.jainSipRegisterRequest = newJainSipRegisterRequest; + this.jainSipMessageFactory.addHeader(this.jainSipRegisterRequest, jainSipAuthorizationHeader); + this.jainSipRegisterTransaction = this.jainSipProvider.getNewClientTransaction(this.jainSipRegisterRequest); + this.jainSipRegisterRequest.setTransaction(this.jainSipRegisterTransaction); + this.jainSipRegisterTransaction.sendRequest(); +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process WebSocket disconnection/close event + * @public + */ +PrivateJainSipClientConnector.prototype.processDisconnected = function() { + console.debug("PrivateJainSipClientConnector:processDisconnected(): SIP connectivity has been lost"); + try { + this.reset(); + this.webRTCommClient.onPrivateClientConnectorClosedEvent(); + } catch (exception) { + console.error("PrivateJainSipClientConnector:processDisconnected(): catched exception:" + exception); + } +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process WebSocket connection error event + * @public + * @param {string} error WebSocket connection error + */ +PrivateJainSipClientConnector.prototype.processConnectionError = function(error) { + console.warn("PrivateJainSipClientConnector:processConnectionError(): SIP connection has failed, error:" + error); + try { + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent(); + } catch (exception) { + console.error("PrivateJainSipClientConnector:processConnectionError(): catched exception:" + exception); + } +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process SIP request event + * @public + * @param {RequestEvent} requestEvent JAIN SIP request event + */ +PrivateJainSipClientConnector.prototype.processRequest = function(requestEvent) { + console.debug("PrivateJainSipClientConnector:processRequest()"); + try { + var jainSipRequest = requestEvent.getRequest(); + console.debug("PrivateJainSipClientConnector:processRequest():jainSipRequest.getCallId().getCallId()=" + jainSipRequest.getCallId().getCallId()); + var jainSipRequestMethod = jainSipRequest.getMethod(); + if (jainSipRequestMethod === "OPTIONS") { + this.processSipOptionRequest(requestEvent); + } else { + // Find related PrivateJainSipCallConnector (subsequent request) + var sipSessionId = jainSipRequest.getCallId().getCallId(); + var sessionConnector = this.sessionConnectors[sipSessionId]; + if (sessionConnector) { + sessionConnector.onJainSipClientConnectorSipRequestEvent(requestEvent); + } else { + if (jainSipRequestMethod === "INVITE") { + // Incoming SIP INVITE + var newWebRTCommCall = new WebRTCommCall(this.webRTCommClient); + newWebRTCommCall.incomingCallFlag = true; + newWebRTCommCall.connector = this.createPrivateSessionConnector(newWebRTCommCall, sipSessionId); + newWebRTCommCall.id = newWebRTCommCall.connector.getId(); + newWebRTCommCall.connector.sipCallState = PrivateJainSipCallConnector.prototype.SIP_INVITED_INITIAL_STATE; + newWebRTCommCall.connector.onJainSipClientConnectorSipRequestEvent(requestEvent); + } else if (jainSipRequestMethod === "MESSAGE") { + // Incoming SIP MESSAGE + // Find WebRTCommCall linked with the message (if exist) + var targetedWebRTCommCall = undefined; + var from = requestEvent.getRequest().getHeader("From").getAddress().getURI().getUser(); + for (var sipCallId in this.sessionConnectors) { + var sessionConnector = this.sessionConnectors[sipCallId]; + if (sessionConnector instanceof PrivateJainSipCallConnector) { + if (sessionConnector.isOpened()) { + if (sessionConnector.webRTCommCall.isIncoming() && sessionConnector.webRTCommCall.callerPhoneNumber === from) { + targetedWebRTCommCall = sessionConnector.webRTCommCall; + break; + } else if (sessionConnector.webRTCommCall.calleePhoneNumber === from) { + targetedWebRTCommCall = sessionConnector.webRTCommCall; + break; + } + } + } + } + + // Build WebRTCommMessage + var newWebRTCommMessage = new WebRTCommMessage(this.webRTCommClient, targetedWebRTCommCall); + newWebRTCommMessage.connector.onJainSipClientConnectorSipRequestEvent(requestEvent); + } else { + console.warn("PrivateJainSipClientConnector:processRequest(): SIP request ignored"); + //@todo Should send SIP response 404 NOT FOUND or 501 NOT_IMPLEMENTED + } + + } + } + } catch (exception) { + console.error("PrivateJainSipClientConnector:processRequest(): catched exception:" + exception); + } +}; + + +/** + * Implementation of JAIN SIP stack event listener interface: process SIP response event + * @public + * @param {ResponseEvent} responseEvent JAIN SIP response event + */ +PrivateJainSipClientConnector.prototype.processResponse = function(responseEvent) { + console.debug("PrivateJainSipClientConnector:processResponse()"); + try { + var jainSipResponse = responseEvent.getResponse(); + if (jainSipResponse.getCSeq().getMethod() === "REGISTER") { + this.processSipRegisterResponse(responseEvent); + } else { + // Find related PrivateJainSipCallConnector + var sipSessionId = jainSipResponse.getCallId().getCallId(); + var sessionConnector = this.sessionConnectors[sipSessionId]; + if (sessionConnector) { + sessionConnector.onJainSipClientConnectorSipResponseEvent(responseEvent); + } else { + console.warn("PrivateJainSipClientConnector:processResponse(): PrivateJainSipCallConnector not found, SIP response ignored"); + } + } + } catch (exception) { + console.error("PrivateJainSipClientConnector:processResponse(): catched exception:" + exception); + } +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process SIP transaction terminated event + * @public + */ +PrivateJainSipClientConnector.prototype.processTransactionTerminated = function() { + console.debug("PrivateJainSipClientConnector:processTransactionTerminated()"); +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process SIP dialog terminated event + * @public + */ +PrivateJainSipClientConnector.prototype.processDialogTerminated = function() { + console.debug("PrivateJainSipClientConnector:processDialogTerminated()"); +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process I/O websocket error event + * @public + * @param {ExceptionEvent} exceptionEvent JAIN SIP exception event + */ +PrivateJainSipClientConnector.prototype.processIOException = function(exceptionEvent) { + console.error("PrivateJainSipClientConnector:processIOException(): exceptionEvent=" + exceptionEvent.message); +}; + +/** + * Implementation of JAIN SIP stack event listener interface: process SIP Dialog Timeout event + * @public + * @param {TimeoutEvent} timeoutEvent JAIN SIP timeout event + */ +PrivateJainSipClientConnector.prototype.processTimeout = function(timeoutEvent) { + console.debug("PrivateJainSipClientConnector:processTimeout():timeoutEvent=" + timeoutEvent); + try { + var sipClientTransaction = timeoutEvent.getClientTransaction(); + // Find related PrivateJainSipCallConnector + var sipCallId = sipClientTransaction.getDialog().getCallId().getCallId(); + var sessionConnector = this.sessionConnectors[sipCallId]; + if (sessionConnector) { + sessionConnector.onJainSipClientConnectorSipTimeoutEvent(timeoutEvent); + } else if (this.jainSipRegisterRequest.getCallId().getCallId() === sipCallId) { + console.error("PrivateJainSipClientConnector:processTimeout(): SIP registration failed, request timeout, no response from SIP server, Call-Id: " + sipCallId); + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent("Request Timeout"); + } else { + console.warn("PrivateJainSipClientConnector:processTimeout(): no dialog found, SIP timeout ignored"); + } + } catch (exception) { + console.error("PrivateJainSipClientConnector:processTimeout(): catched exception:" + exception); + } +}; + +/** + * SIP REGISTER refresh timeout + * @private + */ +PrivateJainSipClientConnector.prototype.onSipRegisterTimeout = function() { + console.debug("PrivateJainSipClientConnector:onSipRegisterTimeout()"); + try { + if (this.sipRegisterState === this.SIP_REGISTERED_STATE) { + this.sipRegisterRefreshTimer = undefined; + this.sipRegisterState = this.SIP_REGISTER_REFRESHING_STATE; + // Send SIP REGISTER request + this.sendNewSipRegisterRequest(this.SIP_SESSION_EXPIRATION_TIMER); + } else { + console.warn("PrivateJainSipClientConnector:onSipRegisterTimeout(): SIP REGISTER refresh stopped"); + } + } catch (exception) { + console.error("PrivateJainSipClientConnector:onSipRegisterTimeout(): catched exception:" + exception); + } +}; + + +/** + * SIP REGISTER state machine + * @private + * @param {ResponseEvent} responseEvent JAIN SIP response to process + */ +PrivateJainSipClientConnector.prototype.processSipRegisterResponse = function(responseEvent) { + console.debug("PrivateJainSipClientConnector:processSipRegisterResponse(): this.sipRegisterState=" + this.sipRegisterState); + + var jainSipResponse = responseEvent.getResponse(); + var statusCode = parseInt(jainSipResponse.getStatusCode()); + if (this.sipRegisterState === this.SIP_UNREGISTERED_STATE) { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): bad state, SIP response ignored"); + } else if ((this.sipRegisterState === this.SIP_REGISTERING_STATE) || (this.sipRegisterState === this.SIP_REGISTER_REFRESHING_STATE)) { + if (statusCode < 200) { + console.debug("PrivateJainSipClientConnector:processSipRegisterResponse(): 1XX response ignored"); + } else if (statusCode === 401 || statusCode === 407) { + if (this.configuration.sipPassword !== undefined && this.configuration.sipLogin !== undefined) { + this.sipRegisterState = this.SIP_REGISTERING_401_STATE; + var jainSipAuthorizationHeader = this.jainSipHeaderFactory.createAuthorizationHeader(jainSipResponse, this.jainSipRegisterRequest, this.configuration.sipPassword, this.configuration.sipLogin); + // Send authenticated SIP REGISTER request + this.sendAuthenticatedSipRegisterRequest(jainSipAuthorizationHeader); + } else { + // Authentification required but not SIP credentials in SIP profile + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): SIP registration failed:" + jainSipResponse.getStatusCode() + " " + jainSipResponse.getStatusLine()); + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent(); + } + } else if (statusCode === 200) { + this.sipRegisterState = this.SIP_REGISTERED_STATE; + if (this.openedFlag === false) { + this.openedFlag = true; + this.webRTCommClient.onPrivateClientConnectorOpenedEvent(); + } + + if (this.sipUnregisterPendingFlag === true) { + this.sipUnregisterPendingFlag = false; + this.sipRegisterState = this.SIP_UNREGISTERING_STATE; + if (this.sipRegisterRefreshTimer) { + // Cancel SIP REGISTER refresh timer + clearTimeout(this.sipRegisterRefreshTimer); + } + this.sendNewSipRegisterRequest(0); + } else { + // Start SIP REGISTER refresh timeout + var that = this; + if (this.sipRegisterRefreshTimer) + clearTimeout(this.sipRegisterRefreshTimer); + this.sipRegisterRefreshTimer = setTimeout(function() { + that.onSipRegisterTimeout(); + }, this.SIP_REGISTER_REFRESH_TIMER * 1000); + } + } else { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): SIP registration failed:" + jainSipResponse.getStatusCode() + " " + jainSipResponse.getStatusLine()); + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent(); + } + } else if (this.sipRegisterState === this.SIP_REGISTERING_401_STATE) { + if (statusCode < 200) { + // No temporary response for SIP REGISTER request + } else if (statusCode === 200) { + this.sipRegisterState = this.SIP_REGISTERED_STATE; + if (this.openedFlag === false) { + console.debug("PrivateJainSipClientConnector:processSipRegisterResponse(): this.openedFlag=true"); + this.openedFlag = true; + this.webRTCommClient.onPrivateClientConnectorOpenedEvent(); + } + + if (this.sipUnregisterPendingFlag === true) { + this.sipUnregisterPendingFlag = false; + this.sipRegisterState = this.SIP_UNREGISTERING_STATE; + if (this.sipRegisterRefreshTimer) { + // Cancel SIP REGISTER refresh timer + clearTimeout(this.sipRegisterRefreshTimer); + } + this.sendNewSipRegisterRequest(0); + } else { + // Start SIP REGISTER refresh timeout + var that = this; + if (this.sipRegisterRefreshTimer) + clearTimeout(this.sipRegisterRefreshTimer); + this.sipRegisterRefreshTimer = setTimeout(function() { + that.onSipRegisterTimeout(); + }, this.SIP_REGISTER_REFRESH_TIMER * 1000); + } + } else { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): SIP registration failed:" + jainSipResponse.getStatusCode() + " " + jainSipResponse.getStatusLine()); + this.reset(); + this.webRTCommClient.onPrivateClientConnectorOpenErrorEvent(); + } + } else if (this.sipRegisterState === this.SIP_REGISTERED_STATE) { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): bad state, SIP response ignored"); + } else if (this.sipRegisterState === this.SIP_UNREGISTERING_STATE) { + if (statusCode < 200) { + // Not temporary response for SIP REGISTER request + } else if (statusCode === 401 || statusCode === 407) { + this.sipRegisterState = this.SIP_UNREGISTERING_401_STATE; + jainSipAuthorizationHeader = this.jainSipHeaderFactory.createAuthorizationHeader(jainSipResponse, this.jainSipRegisterRequest, this.configuration.sipPassword, this.configuration.sipLogin); + this.sendAuthenticatedSipRegisterRequest(jainSipAuthorizationHeader); + } else if (statusCode === 200) { + this.reset(); + this.webRTCommClient.onPrivateClientConnectorClosedEvent(); + } else { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): SIP unregistration failed:" + jainSipResponse.getStatusCode() + " " + jainSipResponse.getStatusLine()); + this.reset(); + this.webRTCommClient.onPrivateClientConnectorClosedEvent(); + } + } else if (this.sipRegisterState === this.SIP_UNREGISTERING_401_STATE) { + if (statusCode < 200) { + // Not temporary response for SIP REGISTER request + } else if (statusCode === 200) { + this.reset(); + this.webRTCommClient.onPrivateClientConnectorClosedEvent(); + } else { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): SIP unregistration failed:" + jainSipResponse.getStatusCode() + " " + jainSipResponse.getStatusLine()); + this.reset(); + this.webRTCommClient.onPrivateClientConnectorClosedEvent(); + } + } else if (this.sipRegisterState === this.SIP_UNREGISTERED_STATE) { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): bad state, SIP response ignored"); + } else { + console.error("PrivateJainSipClientConnector:processSipRegisterResponse(): bad state, SIP response ignored"); + } +}; + + + +/** + * Handle SIP OPTIONS RESPONSE (default behaviour: send 200 OK response) + * @param {RequestEvent} requestEvent JAIN SIP request event to process + * @private + */ +PrivateJainSipClientConnector.prototype.processSipOptionRequest = function(requestEvent) { + console.debug("PrivateJainSipClientConnector:processSipOptionRequest()"); + // Build SIP OPTIONS 200 OK response + var jainSipRequest = requestEvent.getRequest(); + var jainSip200OKResponse = jainSipRequest.createResponse(200, "OK"); + jainSip200OKResponse.addHeader(this.jainSipContactHeader); + jainSip200OKResponse.removeHeader("P-Asserted-Identity"); + jainSip200OKResponse.removeHeader("P-Charging-Vector"); + jainSip200OKResponse.removeHeader("P-Charging-Function-Addresses"); + jainSip200OKResponse.removeHeader("P-Called-Party-ID"); + requestEvent.getServerTransaction().sendResponse(jainSip200OKResponse); +}; +/** + * @class WebRTCommCall + * @classdesc Main class of the WebRTComm Framework providing high level communication management: + * ringing, ringing back, accept, reject, cancel, bye + * @constructor + * @public + * @param {WebRTCommClient} webRTCommClient client owner + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @author Jean Deruelle (jean.deruelle@telestax.com) + */ +WebRTCommCall = function(webRTCommClient) { + if (webRTCommClient instanceof WebRTCommClient) { + console.debug("WebRTCommCall:WebRTCommCall()"); + this.id = undefined; + this.webRTCommClient = webRTCommClient; + this.calleePhoneNumber = undefined; + this.callerPhoneNumber = undefined; + this.callerDisplayName = undefined; + this.customHeaders = undefined; + this.incomingCallFlag = false; + this.configuration = undefined; + this.connector = undefined; + this.peerConnection = undefined; + this.peerConnectionState = undefined; + this.remoteBundledAudioVideoMediaStream = undefined; + this.remoteAudioMediaStream = undefined; + this.remoteVideoMediaStream = undefined; + this.remoteSdpOffer = undefined; + this.messageChannel = undefined; + this.dtmfSender = undefined; + // Set default listener to client listener + this.eventListener = webRTCommClient.eventListener; + this.statsAlreadyRequested = false; + // webrtc media stats (i.e. coming from PeerConnection.getStats()) + this.stats = undefined; + } else { + throw "WebRTCommCall:WebRTCommCall(): bad arguments" + } +}; + +// 'static' to globally tell us if we are on call +WebRTCommCall.onCall = false; + +/** + * Audio Codec Name + * @private + * @constant + */ +WebRTCommCall.prototype.codecNames = { + 0: "PCMU", + 8: "PCMA" +}; + +/** + * Get opened/closed status + * @public + * @returns {boolean} true if opened, false if closed + */ +WebRTCommCall.prototype.isOpened = function() { + if (this.connector) + return this.connector.isOpened(); + else + return false; +}; + +/** + * Get incoming call status + * @public + * @returns {boolean} true if incoming, false if outgoing + */ +WebRTCommCall.prototype.isIncoming = function() { + if (this.isOpened()) { + return this.incomingCallFlag; + } else { + console.error("WebRTCommCall:isIncoming(): bad state, unauthorized action"); + throw "WebRTCommCall:isIncoming(): bad state, unauthorized action"; + } +}; + + + +/** + * Get call ID + * @public + * @returns {String} id + */ +WebRTCommCall.prototype.getId = function() { + return this.id; +}; + +/** + * Get caller phone number + * @public + * @returns {String} callerPhoneNumber or undefined + */ +WebRTCommCall.prototype.getCallerPhoneNumber = function() { + return this.callerPhoneNumber; +}; + +/** + * Get caller display Name + * @public + * @returns {String} callerDisplayName or undefined + */ +WebRTCommCall.prototype.getCallerDisplayName = function() { + return this.callerDisplayName; +}; + +/** + * Get client configuration + * @public + * @returns {object} configuration or undefined + */ +WebRTCommCall.prototype.getConfiguration = function() { + return this.configuration; +}; + + +/** + * Get callee phone number + * @public + * @return {String} calleePhoneNumber or undefined + */ +WebRTCommCall.prototype.getCalleePhoneNumber = function() { + return this.calleePhoneNumber; +}; + +/** + * get bundled audio & video remote media stream + * @public + * @return {MediaStream} remoteBundledAudioVideoMediaStream or undefined + */ +WebRTCommCall.prototype.getRemoteBundledAudioVideoMediaStream = function() { + return this.remoteBundledAudioVideoMediaStream; +}; + +/** + * get remote audio media stream + * @public + * @return {MediaStream} remoteAudioMediaStream or undefined + */ +WebRTCommCall.prototype.getRemoteAudioMediaStream = function() { + return this.remoteAudioMediaStream; +}; + +/** + * get remote audio media stream + * @public + * @return {MediaStream} remoteAudioMediaStream or undefined + */ +WebRTCommCall.prototype.getRemoteVideoMediaStream = function() { + return this.remoteVideoMediaStream; +}; + + +/** + * set webRTCommCall listener + * @param {objet} eventListener implementing WebRTCommCallEventListener interface + */ +WebRTCommCall.prototype.setEventListener = function(eventListener) { + this.eventListener = eventListener; +}; + +/** + * Open WebRTC communication, asynchronous action, opened or error event are notified to the WebRTCommClient eventListener + * @public + * @param {String} calleePhoneNumber callee phone number (bob@sip.net) + * @param {object} configuration communication configuration JSON object + *

Communication configuration sample:
+ * {
+ * displayName:alice,
+ * localMediaStream: [LocalMediaStream],
+ * audioMediaFlag:true,
+ * videoMediaFlag:false,
+ * messageMediaFlag:false,
+ * audioCodecsFilter:PCMA,PCMU,OPUS,
+ * videoCodecsFilter:VP8,H264,
+ * opusFmtpCodecsParameters:maxaveragebitrate=128000,
+ * }
+ *

+ * @throw {String} Exception "bad argument, check API documentation" + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception internal error + */ +WebRTCommCall.prototype.open = function(calleePhoneNumber, configuration) { + console.debug("WebRTCommCall:open():calleePhoneNumber=" + calleePhoneNumber); + console.debug("WebRTCommCall:open():configuration=" + JSON.stringify(configuration)); + if (typeof(configuration) === 'object') { + if (this.webRTCommClient.isOpened()) { + // check if we are already on call + if (WebRTCommCall.onCall) { + //console.error("WebRTCommClient:call(): call already ongoing"); + throw "WebRTCommClient:call(): call already ongoing"; + } + WebRTCommCall.onCall = true; + + if (this.checkConfiguration(configuration)) { + if (this.isOpened() === false) { + try { + var that = this; + this.callerPhoneNumber = this.webRTCommClient.configuration.sip.sipUserName; + this.calleePhoneNumber = calleePhoneNumber; + this.configuration = configuration; + this.connector.open(configuration); + + // Setup RTCPeerConnection first + this.createRTCPeerConnection(); + //////////// + //this.get_stats(); + if (configuration.audioMediaFlag || configuration.videoMediaFlag) { + console.debug("[PC]: addStream()"); + this.peerConnection.addStream(this.configuration.localMediaStream); + } + if (this.configuration.messageMediaFlag) { + if (this.peerConnection.createDataChannel) { + try { + console.debug("[PC]: createDataChannel()"); + this.messageChannel = this.peerConnection.createDataChannel("mymessageChannel", { + reliable: false + }); + console.debug("WebRTCommCall:open(): this.messageChannel.label=" + this.messageChannel.label); + console.debug("WebRTCommCall:open(): this.messageChannel.reliable=" + this.messageChannel.reliable); + console.debug("WebRTCommCall:open(): this.messageChannel.binaryType=" + this.messageChannel.binaryType); + this.messageChannel.onopen = function(event) { + that.onRtcPeerConnectionMessageChannelOpenEvent(event); + }; + this.messageChannel.onclose = function(event) { + that.onRtcPeerConnectionMessageChannelClose(event); + }; + this.messageChannel.onerror = function(event) { + that.onRtcPeerConnectionMessageChannelErrorEvent(event); + }; + this.messageChannel.onmessage = function(event) { + that.onRtcPeerConnectionMessageChannelMessageEvent(event); + }; + } catch (exception) { + alert("WebRTCommCall:open():DataChannel not supported"); + } + } + } + + if (window.webkitRTCPeerConnection) { + var sdpConstraints = { + mandatory: { + OfferToReceiveAudio: this.configuration.audioMediaFlag, + OfferToReceiveVideo: this.configuration.videoMediaFlag + }, + optional: [] + }; + + console.debug("WebRTCommCall:open():sdpConstraints=" + JSON.stringify(sdpConstraints)); + console.debug("[PC]: createOffer()"); + this.peerConnection.createOffer(function(offer) { + that.onRtcPeerConnectionCreateOfferSuccessEvent(offer); + }, function(error) { + that.onRtcPeerConnectionCreateOfferErrorEvent(error); + }, sdpConstraints); + } else if (window.mozRTCPeerConnection) { + var sdpConstraints = { + offerToReceiveAudio: this.configuration.audioMediaFlag, + offerToReceiveVideo: this.configuration.videoMediaFlag, + mozDontOfferDataChannel: !this.configuration.messageMediaFlag + }; + + console.debug("WebRTCommCall:open():sdpConstraints=" + JSON.stringify(sdpConstraints)); + console.debug("[PC]: createOffer()"); + this.peerConnection.createOffer(function(offer) { + that.onRtcPeerConnectionCreateOfferSuccessEvent(offer); + }, function(error) { + that.onRtcPeerConnectionCreateOfferErrorEvent(error); + }, sdpConstraints); + } + console.debug("WebRTCommCall:open():sdpConstraints=" + JSON.stringify(sdpConstraints)); + } catch (exception) { + console.error("WebRTCommCall:open(): catched exception:" + exception); + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallOpenErrorEvent(that, exception); + } catch (exception) { + console.error("WebRTCommCall:open(): catched exception in listener:" + exception); + } + }, 1); + // Close properly the communication + try { + + this.close(false); + } catch (e) {} + throw exception; + } + } else { + console.error("WebRTCommCall:open(): bad state, unauthorized action"); + throw "WebRTCommCall:open(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommCall:open(): bad configuration"); + throw "WebRTCommCall:open(): bad configuration"; + } + } else { + console.error("WebRTCommCall:open(): bad state, unauthorized action"); + throw "WebRTCommCall:open(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommCall:open(): bad argument, check API documentation"); + throw "WebRTCommCall:open(): bad argument, check API documentation" + } +}; + + +/** + * Close WebRTC communication, asynchronous action, closed event are notified to the WebRTCommClient eventListener + * @public + * @throw {String} Exception "bad state, unauthorized action" + */ +WebRTCommCall.prototype.hangup = function() { + console.debug("WebRTCommCall:hangup()"); + if (this.webRTCommClient.isOpened()) { + try { + // Close private Call Connector + if (this.connector) { + this.connector.close(); + } + + // Close RTCPeerConnection + if (this.peerConnection && this.peerConnection.signalingState !== 'closed') { + if (this.messageChannel) + this.messageChannel.close(); + console.debug("[PC]: close()"); + this.peerConnection.close(); + this.peerConnection = undefined; + this.dtmfSender = undefined; + // Notify asynchronously the closed event + var that = this; + setTimeout(function() { + // stats are delivered as part of 'that' + that.eventListener.onWebRTCommCallClosedEvent(that); + // stats delivered, let's reset the flag + this.statsAlreadyRequested = false; + }, 1); + } + } catch (exception) { + console.error("WebRTCommCall:close(): catched exception:" + exception); + } + } else { + console.error("WebRTCommCall:close(): bad state, unauthorized action"); + throw "WebRTCommCall:close(): bad state, unauthorized action"; + } +} + +// Dumping a stats variable as a string. +// might be named toString? +function dumpStats(results) { + var statsString = ''; + Object.keys(results).forEach(function(key, index) { + var res = results[key]; + statsString += 'Report '; + statsString += index; + statsString += ': time ' + res.timestamp; + statsString += ', type ' + res.type + '
\n'; + Object.keys(res).forEach(function(k) { + if (k !== 'timestamp' && k !== 'type') { + statsString += k + ': ' + res[k] + '
\n'; + } + }); + statsString += '
\n'; + }); + return statsString; +} + +/** + * Take as input getStats() outcome and convert to specific metrics that we are mostly interested. Also in the process normalize mozilla & chrome format + * @public + * @param {shouldGetStats} should we collect webrtc media stats? Boolean, default true + * @throw {String} Exception "bad state, unauthorized action" + * @returns {Array of objects} Each object corresponds to a + * media-type/direction pair. So if we have audio & video call we would have an + * array of 4 objects: 1. audio/inbound, audio/outbound, video/inbound, + * video/outbound. Each object has the following keys: + * + * media-type: audio/video (ff only, until we figure it out for chrome) + * direction: inbound/outbound + * bitrate: bitrate in kbit/sec, like 250 kbit/sec (ff only) + * packetsLost: lost packet count + * bytesTransfered: bytes sent/received + * packetsTransfered: packets sent/received + * jitter: jitter for incoming packets + * ssrc: synchronization source for this stream, like 501954246 + */ +WebRTCommCall.prototype.normalizeStats = function(stats) { + //var statsString = dumpStats(stats); + //console.error('--------: ' + JSON.stringify(statsString)); + + // array of objects + var normalizedStats = []; + // calculate video bitrate + Object.keys(stats).forEach(function(result) { + var report = stats[result]; + //var now = report.timestamp; + + // object to represent stats for a single media type (i.e. audio/video) and a single direction (i.e. inbound/outbound) + normalizedStat = {}; + if (/boundrtp$/.test(report.type)) { + // firefox + if (report.type === 'inboundrtp') { + normalizedStat['direction'] = 'inbound'; + normalizedStat['bytes-transfered'] = report.bytesReceived; + normalizedStat['packets-transfered'] = report.packetsReceived; + } + if (report.type === 'outboundrtp') { + normalizedStat['direction'] = 'outbound'; + normalizedStat['bytes-transfered'] = report.bytesSent; + normalizedStat['packets-transfered'] = report.packetsSent; + } + normalizedStat['media-type'] = report.mediaType; + if (report.bitrateMean) { + normalizedStat['bitrate'] = Math.floor(report.bitrateMean / 1024); + } + normalizedStat['packets-lost'] = report.packetsLost; + normalizedStat['jitter'] = report.jitter; + normalizedStat['ssrc'] = report.ssrc; + + normalizedStats.push(normalizedStat); + } + else if (report.type === 'ssrc') { + // chrome + if (/_recv$/.test(report.id)) { + normalizedStat['direction'] = 'inbound'; + normalizedStat['bytes-transfered'] = report.bytesReceived; + normalizedStat['packets-transfered'] = report.packetsReceived; + if (report.audioOutputLevel) { + // only applies to audio, so let's use it as indication for media-type + normalizedStat['output-level'] = report.audioOutputLevel; + normalizedStat['media-type'] = 'audio'; + } + if (report.googFrameHeightReceived) { + // only applies to video, so let's use it as indication for media-type + normalizedStat['media-type'] = 'video'; + } + } + if (/_send$/.test(report.id)) { + normalizedStat['direction'] = 'outbound'; + normalizedStat['bytes-transfered'] = report.bytesSent; + normalizedStat['packets-transfered'] = report.packetsSent; + if (report.audioInputLevel) { + // only applies to audio, so let's use it as indication for media-type + normalizedStat['input-level'] = report.audioInputLevel; + normalizedStat['media-type'] = 'audio'; + } + if (report.googFrameHeightSent) { + // only applies to video, so let's use it as indication for media-type + normalizedStat['media-type'] = 'video'; + } + } + normalizedStat['codec-name'] = report.googCodecName; + normalizedStat['packets-lost'] = report.packetsLost; + normalizedStat['jitter'] = report.googJitterReceived; + normalizedStat['ssrc'] = report.ssrc; + // TODO: need to find a way to figure out the media-type for chrome + + + normalizedStats.push(normalizedStat); + } + }); + + return normalizedStats; +} + +/** + * Close WebRTC communication, asynchronous action, closed event are notified to the WebRTCommClient eventListener. Notice that the actual close happens in this.hangup(), reason for separating those is that we need to close after we have received the webrtc stats (that is if they have been requested), cause otherwise getStats() might fail, since PeerConnection might be freed first + * @public + * @param {shouldGetStats} should we collect webrtc media stats? Boolean, default true + * @throw {String} Exception "bad state, unauthorized action" + */ +WebRTCommCall.prototype.close = function(shouldGetStats) { + // user requested to hangup the call, let's gather media stats before doing so if user asked for it + if (typeof shouldGetStats === 'undefined') { + shouldGetStats = true; + } + + if (shouldGetStats === true) { + if (this.peerConnection != null && this.statsAlreadyRequested === false) { + var that = this; + this.statsAlreadyRequested = true; + console.debug("[PC]: getStats()"); + this.peerConnection.getStats(null, function(results) { + console.debug("WebRTCommCall:close(), received media stats"); + // do actual hangup now that we got the stats + that.hangup(); + + // normalize the stats + that.stats = that.normalizeStats(results); + }, function(err) { + console.log(err); + }); + } + } else { + console.debug("WebRTCommCall:close(), with no media stats"); + this.hangup(); + } + + WebRTCommCall.onCall = false; +}; + +/** + * Return PeerConnection stats on Demand + * @public + * @throw {String} Exception "bad state, unauthorized action" + */ +WebRTCommCall.prototype.getStats = function() { + console.debug("WebRTCommCall:getStats()"); + if (this.peerConnection != null && this.statsAlreadyRequested === false) { + var that = this; + this.statsAlreadyRequested = true; + console.debug("[PC]: getStats()"); + this.peerConnection.getStats(null, function(results) { + that.statsAlreadyRequested = false; + console.debug("WebRTCommCall:getStats(), received media stats"); + + // normalize the stats + var stats = that.normalizeStats(results); + + // notify the webrtcomm listener of the stats + if (that.eventListener.onWebRTCommCallStatsEvent) { + console.debug("WebRTCommCall:getStats(), notifying caller"); + that.eventListener.onWebRTCommCallStatsEvent(that, stats); + } + }, function(err) { + console.log(err); + }); + } +} + +/** + * Return PeerConnection stats on Demand + * @public + * @throw {String} Exception "bad state, unauthorized action" + */ +/* +WebRTCommCall.prototype.getStats = function() { + if (this.peerConnection != null && this.statsAlreadyRequested === false) { + var that = this; + this.statsAlreadyRequested = true; + this.peerConnection.getStats(null, function(results) { + console.debug("WebRTCommCall:getStats(), received media stats"); + + // normalize the stats + that.stats = that.normalizeStats(results); + + setTimeout(function() { + that.peerConnection.getStats(null, function(results) { + console.debug("WebRTCommCall:getStats(), received media stats after delay"); + + // normalize the stats + newStats = that.normalizeStats(results); + + // calculate bitrate from old and new stats + bitrate = 8 * (bytes - bytesPrev) / (now - timestampPrev); + bitrate = Math.floor(bitrate); + + // notify the webrtcomm listener of the stats + if (that.eventListener.onWebRTCommCallStatsEvent) { + console.debug("WebRTCommCall:getStats(), notifying caller"); + that.eventListener.onWebRTCommCallStatsEvent(that, newStats); + } + that.statsAlreadyRequested = false; + }, function(err) { + console.log(err); + that.statsAlreadyRequested = false; + }); + + // notify the webrtcomm listener of the stats + }, 1000); + }, function(err) { + console.log(err); + }); + } +} +*/ + +/** + * Accept incoming WebRTC communication + * @public + * @param {object} configuration communication configuration JSON object + *

Communication configuration sample:
+ * {
+ * displayName:alice,
+ * localMediaStream: [LocalMediaStream],
+ * audioMediaFlag:true,
+ * videoMediaFlag:false,
+ * messageMediaFlag:false,
+ * audioCodecsFilter:PCMA,PCMU,OPUS,
+ * videoCodecsFilter:VP8,H264,
+ * }
+ *

+ * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + */ +WebRTCommCall.prototype.accept = function(configuration) { + console.debug("WebRTCommCall:accept():configuration=" + JSON.stringify(configuration)); + if (typeof(configuration) === 'object') { + if (this.webRTCommClient.isOpened()) { + if (this.checkConfiguration(configuration)) { + this.configuration = configuration; + if (this.isOpened() === false) { + try { + this.createRTCPeerConnection(); + if (configuration.audioMediaFlag || configuration.videoMediaFlag) { + console.debug("[PC]: addStream()"); + this.peerConnection.addStream(this.configuration.localMediaStream); + } + var sdpOffer = undefined; + //if (window.webkitRTCPeerConnection) { + sdpOffer = new RTCSessionDescription({ + type: 'offer', + sdp: this.remoteSdpOffer + }); + /* + } else if (window.mozRTCPeerConnection) { + sdpOffer = new mozRTCSessionDescription({ + type: 'offer', + sdp: this.remoteSdpOffer + }); + } + */ + var that = this; + this.peerConnectionState = 'offer-received'; + console.debug("[PC]: setRemoteDescription()"); + this.peerConnection.setRemoteDescription(sdpOffer, function() { + that.onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(); + }, function(error) { + that.onRtcPeerConnectionSetRemoteDescriptionErrorEvent(error); + }); + } catch (exception) { + console.error("WebRTCommCall:accept(): catched exception:" + exception); + // Close properly the communication + try { + this.close(false); + } catch (e) {} + throw exception; + } + } else { + console.error("WebRTCommCall:accept(): bad state, unauthorized action"); + throw "WebRTCommCall:accept(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommCall:accept(): bad configuration"); + throw "WebRTCommCall:accept(): bad configuration"; + } + } else { + console.error("WebRTCommCall:accept(): bad state, unauthorized action"); + throw "WebRTCommCall:accept(): bad state, unauthorized action"; + } + } else { + // Client closed + console.error("WebRTCommCall:accept(): bad argument, check API documentation"); + throw "WebRTCommCall:accept(): bad argument, check API documentation" + } +}; + +/** + * Reject/refuse incoming WebRTC communication + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + */ +WebRTCommCall.prototype.reject = function() { + console.debug("WebRTCommCall:reject()"); + if (this.webRTCommClient.isOpened()) { + try { + this.connector.reject(); + + // Notify asynchronously the closed event + var that = this; + setTimeout(function() { + that.eventListener.onWebRTCommCallClosedEvent(that); + }, 1); + } catch (exception) { + console.error("WebRTCommCall:reject(): catched exception:" + exception); + // Close properly the communication + try { + this.close(false); + } catch (e) {} + throw exception; + } + } else { + console.error("WebRTCommCall:reject(): bad state, unauthorized action"); + throw "WebRTCommCall:reject(): bad state, unauthorized action"; + } +}; + +/** + * Ignore incoming WebRTC communication. This means that we silently close the communication without replying to the remote party + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + */ +WebRTCommCall.prototype.ignore = function() { + console.debug("WebRTCommCall:ignore()"); + if (this.webRTCommClient.isOpened()) { + try { + this.connector.ignore(); + + // Notify asynchronously the closed event + var that = this; + setTimeout(function() { + that.eventListener.onWebRTCommCallClosedEvent(that); + }, 1); + } catch (exception) { + console.error("WebRTCommCall:ignore(): catched exception:" + exception); + // Close properly the communication + try { + this.close(false); + } catch (e) {} + throw exception; + } + } else { + console.error("WebRTCommCall:ignore(): bad state, unauthorized action"); + throw "WebRTCommCall:ignore(): bad state, unauthorized action"; + } +}; + +/** + * Send DTMF Tone to WebRTC communication peer over the peerconnection + * @public + * @param {String} dtmfEvent to send (1,2,3...) + */ +WebRTCommCall.prototype.sendDTMF = function(dtmfEvent) { + var duration = 500; + var gap = 50; + if (this.dtmfSender) { + console.debug('Sending Tones, duration, gap: ', dtmfEvent, duration, gap); + this.dtmfSender.insertDTMF(dtmfEvent, duration, gap); + } else { + console.debug('DTMFSender not initialized, falling back to SIP INFO DTMF, Sending tones, duration, gap: ', dtmfEvent, duration, gap); + this.connector.sendSipDtmf(dtmfEvent); + } +} + + +/** + * Send Short message to WebRTC communication peer + * Use WebRTC datachannel if open otherwise use transport (e.g SIP) implemented by the connector + * @public + * @param {String} text message to send + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + * @returns {WebRTCommMessage} new created WebRTCommMessage object + */ +WebRTCommCall.prototype.sendMessage = function(text) { + console.debug("WebRTCommCall:sendMessage()"); + if (this.webRTCommClient.isOpened()) { + if (this.isOpened()) { + var newWebRTCommMessage = new WebRTCommMessage(this.webRTCommClient, this); + newWebRTCommMessage.text = text; + if (this.isIncoming()) { + newWebRTCommMessage.to = this.callerPhoneNumber; + } else { + newWebRTCommMessage.to = this.calleePhoneNumber; + } + + try { + newWebRTCommMessage.connector.send(); + } catch (exception) { + console.error("WebRTCommCall:sendMessage(): catched exception:" + exception); + throw "WebRTCommCall:sendMessage(): catched exception:" + exception; + } + return newWebRTCommMessage; + } else { + console.error("WebRTCommCall:sendMessage(): bad state, unauthorized action"); + throw "WebRTCommCall:sendMessage(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommCall:sendMessage(): bad state, unauthorized action"); + throw "WebRTCommCall:sendMessage(): bad state, unauthorized action"; + } +}; + +/** + * Send Short message to WebRTC communication peer + * Use WebRTC datachannel if open otherwise use transport (e.g SIP) implemented by the connector + * @public + * @param {String} text message to send + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "internal error,check console logs" + * @returns {WebRTCommMessage} new created WebRTCommMessage object + */ +WebRTCommCall.prototype.sendDataMessage = function(text) { + console.debug("WebRTCommCall:sendDataMessage()"); + if (this.webRTCommClient.isOpened()) { + if (this.isOpened()) { + var newWebRTCommDataMessage = new WebRTCommDataMessage(this.webRTCommClient, this); + newWebRTCommDataMessage.content = text; + if (this.isIncoming()) { + newWebRTCommDataMessage.to = this.callerPhoneNumber; + } else { + newWebRTCommDataMessage.to = this.calleePhoneNumber; + } + + if (this.messageChannel && this.messageChannel.readyState === "open") { + try { + this.messageChannel.send(newWebRTCommDataMessage.content); + if (this.eventListener.onWebRTCommDataMessageSentEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommDataMessageSentEvent(newWebRTCommDataMessage); + } catch (exception) { + console.error("WebRTCommCall:sendDataMessage(): catched exception in event listener:" + exception); + } + }, 1); + } + } catch (exception) { + console.error("WebRTCommCall:sendDataMessage(): catched exception:" + exception); + throw "WebRTCommCall:sendDataMessage(): catched exception:" + exception; + } + } + + return newWebRTCommDataMessage; + } else { + console.error("WebRTCommCall:sendDataMessage(): bad state, unauthorized action"); + throw "WebRTCommCall:sendDataMessage(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommCall:sendDataMessage(): bad state, unauthorized action"); + throw "WebRTCommCall:sendDataMessage(): bad state, unauthorized action"; + } +}; + +/** + * Mute local audio media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.muteLocalAudioMediaStream = function() { + console.debug("WebRTCommCall:muteLocalAudioMediaStream()"); + if (this.configuration.localMediaStream && this.configuration.localMediaStream.signalingState === this.configuration.localMediaStream.LIVE) { + var audioTracks = undefined; + if (this.configuration.localMediaStream.audioTracks) + audioTracks = this.configuration.localMediaStream.audioTracks; + else if (this.configuration.localMediaStream.getAudioTracks) + audioTracks = this.configuration.localMediaStream.getAudioTracks(); + if (audioTracks) { + for (var i = 0; i < audioTracks.length; i++) { + audioTracks[i].enabled = false; + } + } else { + console.error("WebRTCommCall:muteLocalAudioMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:muteLocalAudioMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:muteLocalAudioMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:muteLocalAudioMediaStream(): bad state, unauthorized action"; + } +}; + +/** + * Unmute local audio media stream + * @public + */ +WebRTCommCall.prototype.unmuteLocalAudioMediaStream = function() { + console.debug("WebRTCommCall:unmuteLocalAudioMediaStream()"); + if (this.configuration.localMediaStream && this.configuration.localMediaStream.signalingState === this.configuration.localMediaStream.LIVE) { + var audioTracks = undefined; + if (this.configuration.localMediaStream.audioTracks) + audioTracks = this.configuration.localMediaStream.audioTracks; + else if (this.configuration.localMediaStream.getAudioTracks) + audioTracks = this.configuration.localMediaStream.getAudioTracks(); + if (audioTracks) { + for (var i = 0; i < audioTracks.length; i++) { + audioTracks[i].enabled = true; + } + } else { + console.error("WebRTCommCall:unmuteLocalAudioMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:unmuteLocalAudioMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:unmuteLocalAudioMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:unmuteLocalAudioMediaStream(): bad state, unauthorized action"; + } +}; + +/** + * Mute remote audio media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.muteRemoteAudioMediaStream = function() { + console.debug("WebRTCommCall:muteRemoteAudioMediaStream()"); + if (this.remoteBundledAudioVideoMediaStream && this.remoteBundledAudioVideoMediaStream.signalingState === this.remoteBundledAudioVideoMediaStream.LIVE) { + var audioTracks = undefined; + if (this.remoteBundledAudioVideoMediaStream.audioTracks) + audioTracks = this.remoteBundledAudioVideoMediaStream.audioTracks; + else if (this.remoteBundledAudioVideoMediaStream.getAudioTracks) + audioTracks = this.remoteBundledAudioVideoMediaStream.getAudioTracks(); + if (audioTracks) { + for (var i = 0; i < audioTracks.length; i++) { + audioTracks[i].enabled = false; + } + } else { + console.error("WebRTCommCall:muteRemoteAudioMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:muteRemoteAudioMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:muteRemoteAudioMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:muteRemoteAudioMediaStream(): bad state, unauthorized action"; + } +}; + +/** + * Unmute remote audio media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.unmuteRemoteAudioMediaStream = function() { + console.debug("WebRTCommCall:unmuteRemoteAudioMediaStream()"); + if (this.remoteBundledAudioVideoMediaStream && this.remoteBundledAudioVideoMediaStream.signalingState === this.remoteBundledAudioVideoMediaStream.LIVE) { + var audioTracks = undefined; + if (this.remoteBundledAudioVideoMediaStream.audioTracks) + audioTracks = this.remoteBundledAudioVideoMediaStream.audioTracks; + else if (this.remoteBundledAudioVideoMediaStream.getAudioTracks) + audioTracks = this.remoteBundledAudioVideoMediaStream.getAudioTracks(); + if (audioTracks) { + for (var i = 0; i < audioTracks.length; i++) { + audioTracks[i].enabled = true; + } + } else { + console.error("WebRTCommCall:unmuteRemoteAudioMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:unmuteRemoteAudioMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:unmuteRemoteAudioMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:unmuteRemoteAudioMediaStream(): bad state, unauthorized action"; + } +}; + +/** + * Hide local video media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.hideLocalVideoMediaStream = function() { + console.debug("WebRTCommCall:hideLocalVideoMediaStream()"); + if (this.configuration.localMediaStream && this.configuration.localMediaStream.signalingState === this.configuration.localMediaStream.LIVE) { + var videoTracks = undefined; + if (this.configuration.localMediaStream.videoTracks) + videoTracks = this.configuration.localMediaStream.videoTracks; + else if (this.configuration.localMediaStream.getVideoTracks) + videoTracks = this.configuration.localMediaStream.getVideoTracks(); + if (videoTracks) { + videoTracks.enabled = !videoTracks.enabled; + for (var i = 0; i < videoTracks.length; i++) { + videoTracks[i].enabled = false; + } + } else { + console.error("WebRTCommCall:hideLocalVideoMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:hideLocalVideoMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:hideLocalVideoMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:hideLocalVideoMediaStream(): bad state, unauthorized action"; + } +}; + +/** + * Show local video media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.showLocalVideoMediaStream = function() { + console.debug("WebRTCommCall:showLocalVideoMediaStream()"); + if (this.configuration.localMediaStream && this.configuration.localMediaStream.signalingState === this.configuration.localMediaStream.LIVE) { + var videoTracks = undefined; + if (this.configuration.localMediaStream.videoTracks) + videoTracks = this.configuration.localMediaStream.videoTracks; + else if (this.configuration.localMediaStream.getVideoTracks) + videoTracks = this.configuration.localMediaStream.getVideoTracks(); + if (videoTracks) { + videoTracks.enabled = !videoTracks.enabled; + for (var i = 0; i < videoTracks.length; i++) { + videoTracks[i].enabled = true; + } + } else { + console.error("WebRTCommCall:showLocalVideoMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:showLocalVideoMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:showLocalVideoMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:showLocalVideoMediaStream(): bad state, unauthorized action"; + } +}; + + +/** + * Hide remote video media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.hideRemoteVideoMediaStream = function() { + console.debug("WebRTCommCall:hideRemoteVideoMediaStream()"); + if (this.remoteBundledAudioVideoMediaStream && this.remoteBundledAudioVideoMediaStream.signalingState === this.remoteBundledAudioVideoMediaStream.LIVE) { + var videoTracks = undefined; + if (this.remoteBundledAudioVideoMediaStream.videoTracks) + videoTracks = this.remoteBundledAudioVideoMediaStream.videoTracks; + else if (this.remoteBundledAudioVideoMediaStream.getVideoTracks) + videoTracks = this.remoteBundledAudioVideoMediaStream.getVideoTracks(); + if (videoTracks) { + videoTracks.enabled = !videoTracks.enabled; + for (var i = 0; i < videoTracks.length; i++) { + videoTracks[i].enabled = false; + } + } else { + console.error("WebRTCommCall:hideRemoteVideoMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:hideRemoteVideoMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:hideRemoteVideoMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:hideRemoteVideoMediaStream(): bad state, unauthorized action"; + } +}; + +/** + * Show remote video media stream + * @public + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception "not implemented by navigator" + */ +WebRTCommCall.prototype.showRemoteVideoMediaStream = function() { + console.debug("WebRTCommCall:showRemoteVideoMediaStream()"); + if (this.remoteBundledAudioVideoMediaStream && this.remoteBundledAudioVideoMediaStream.signalingState === this.remoteBundledAudioVideoMediaStream.LIVE) { + var videoTracks = undefined; + if (this.remoteBundledAudioVideoMediaStream.videoTracks) + videoTracks = this.remoteBundledAudioVideoMediaStream.videoTracks; + else if (this.remoteBundledAudioVideoMediaStream.getVideoTracks) + videoTracks = this.remoteBundledAudioVideoMediaStream.getVideoTracks(); + if (videoTracks) { + videoTracks.enabled = !videoTracks.enabled; + for (var i = 0; i < videoTracks.length; i++) { + videoTracks[i].enabled = true; + } + } else { + console.error("WebRTCommCall:showRemoteVideoMediaStream(): not implemented by navigator"); + throw "WebRTCommCall:showRemoteVideoMediaStream(): not implemented by navigator"; + } + } else { + console.error("WebRTCommCall:showRemoteVideoMediaStream(): bad state, unauthorized action"); + throw "WebRTCommCall:showRemoteVideoMediaStream(): bad state, unauthorized action"; + } +}; + + +/** + * Check configuration + * @private + * @param {object} configuration call configuration + * @return true configuration ok false otherwise + */ +WebRTCommCall.prototype.checkConfiguration = function(configuration) { + console.debug("WebRTCommCall:checkConfiguration()"); + + var check = true; + // displayName, audioCodecsFilter, videoCodecsFilter NOT mandatoty in configuration + + if (configuration.audioMediaFlag === undefined || (typeof(configuration.audioMediaFlag) !== 'boolean')) { + check = false; + console.error("WebRTCommCall:checkConfiguration(): missing audio media flag"); + } + + if (configuration.videoMediaFlag === undefined || (typeof(configuration.videoMediaFlag) !== 'boolean')) { + check = false; + console.error("WebRTCommCall:checkConfiguration(): missing video media flag"); + } + + if ((configuration.audioMediaFlag || configuration.videoMediaFlag) && configuration.localMediaStream === undefined) { + check = false; + console.error("WebRTCommCall:checkConfiguration(): missing localMediaStream"); + } + + if (configuration.messageMediaFlag === undefined || (typeof(configuration.messageMediaFlag) !== 'boolean')) { + check = false; + console.error("WebRTCommCall:checkConfiguration(): missing message media flag"); + } + return check; +}; + +/** + * Create RTCPeerConnection + * @private + * @return true configuration ok false otherwise + */ +WebRTCommCall.prototype.createRTCPeerConnection = function() { + console.debug("WebRTCommCall:createPeerConnection()"); + var rtcPeerConnectionConfiguration = { + iceServers: [] + }; + + this.peerConnectionState = 'new'; + var that = this; + /* https://code.google.com/p/webrtcomm/issues/detail?id=14 */ + if (this.webRTCommClient.configuration.RTCPeerConnection.iceServers) { + rtcPeerConnectionConfiguration = this.webRTCommClient.configuration.RTCPeerConnection.iceServers; + } else { + if (this.webRTCommClient.configuration.RTCPeerConnection.stunServer) { + rtcPeerConnectionConfiguration.iceServers.push({ + url: "stun:" + this.webRTCommClient.configuration.RTCPeerConnection.stunServer + }); + } + if (this.webRTCommClient.configuration.RTCPeerConnection.turnServer && this.webRTCommClient.configuration.RTCPeerConnection.turnLogin && this.webRTCommClient.configuration.RTCPeerConnection.turnPassword) { + rtcPeerConnectionConfiguration.iceServers.push({ + url: "turn:" + this.webRTCommClient.configuration.RTCPeerConnection.turnServer, + username: this.webRTCommClient.configuration.RTCPeerConnection.turnLogin, + credential: this.webRTCommClient.configuration.RTCPeerConnection.turnPassword + }); + } + } + + console.debug("WebRTCommCall:createPeerConnection():rtcPeerConnectionConfiguration=" + JSON.stringify(rtcPeerConnectionConfiguration)); + + var iceTransports = "all"; + if (this.webRTCommClient.configuration.RTCPeerConnection.forceTurnMediaRelay) { + iceTransports = "relay"; + } + + rtcPeerConnectionConfiguration.iceTransportPolicy = iceTransports; + + console.debug("[PC]: new RTCPeerConnection()"); + this.peerConnection = new RTCPeerConnection(rtcPeerConnectionConfiguration, null); + + this.peerConnection.onaddstream = function(event) { + that.onRtcPeerConnectionAddStreamEvent(event); + }; + + this.peerConnection.onremovestream = function(event) { + that.onRtcPeerConnectionRemoveStreamEvent(event); + }; + + this.peerConnection.onsignalingstatechange = function(event) { + that.onRtcPeerConnectionSignalingStateChangeEvent(event); + }; + + this.peerConnection.onicecandidate = function(rtcIceCandidateEvent) { + that.onRtcPeerConnectionIceCandidateEvent(rtcIceCandidateEvent); + }; + + /* Found in spec, but not implemented by chrome and firefox, so let's leave it out for now. Instead onicecandidate with event.candidate == null can be used + this.peerConnection.onicegatheringstatechange = function(event) { + that.onRtcPeerConnectionGatheringStateChangeEvent(event); + }; + */ + + this.peerConnection.oniceconnectionstatechange = function(event) { + that.onRtcPeerConnectionIceConnectionStateChangeEvent(event); + }; + + this.peerConnection.onopen = function(event) { + that.onRtcPeerConnectionOpenEvent(event); + }; + + this.peerConnection.onidentityresult = function(event) { + that.onRtcPeerConnectionIdentityResultEvent(event); + }; + + /* Obsolete + this.peerConnection.onnegotiationneeded= function(event) { + that.onRtcPeerConnectionIceNegotiationNeededEvent(event); + }*/ + + this.peerConnection.ondatachannel = function(event) { + that.onRtcPeerConnectionOnMessageChannelEvent(event); + }; + + console.debug("WebRTCommCall:createPeerConnection(): this.peerConnection=" + JSON.stringify(this.peerConnection)); +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process remote SDP offer event + * @private + * @param {string} remoteSdpOffer Remote peer SDP offer + */ +WebRTCommCall.prototype.onPrivateCallConnectorRemoteSdpOfferEvent = function(remoteSdpOffer) { + console.debug("WebRTCommCall:onPrivateCallConnectorSdpOfferEvent()"); + this.remoteSdpOffer = remoteSdpOffer; +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process remote SDP answer event + * @private + * @param {string} remoteSdpAnswer + * @throw exception internal error + */ +WebRTCommCall.prototype.onPrivateCallConnectorRemoteSdpAnswerEvent = function(remoteSdpAnswer) { + console.debug("WebRTCommCall:onPrivateCallConnectorRemoteSdpAnswerEvent()"); + try { + var sdpAnswer = undefined; + //if (window.webkitRTCPeerConnection) { + sdpAnswer = new RTCSessionDescription({ + type: 'answer', + sdp: remoteSdpAnswer + }); + /* + } else if (window.mozRTCPeerConnection) { + sdpAnswer = new mozRTCSessionDescription({ + type: 'answer', + sdp: remoteSdpAnswer + }); + } + */ + + var that = this; + this.peerConnectionState = 'answer-received'; + console.debug("[PC]: setRemoteDescription()"); + this.peerConnection.setRemoteDescription(sdpAnswer, function() { + that.onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(); + }, function(error) { + that.onRtcPeerConnectionSetRemoteDescriptionErrorEvent(error); + }); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorRemoteSdpAnswerEvent(): catched exception:" + exception); + throw exception; + } +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process call opened event + * @private + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallOpenedEvent = function() { + console.debug("WebRTCommCall:onPrivateCallConnectorCallOpenedEvent()"); + // Notify event to the listener + if (this.eventListener.onWebRTCommCallOpenEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallOpenEvent(that); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallOpenedEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process call in progress event + * @private + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallInProgressEvent = function() { + console.debug("WebRTCommCall:onPrivateCallConnectorCallInProgressEvent()"); + // Notify event to the listener + if (this.eventListener.onWebRTCommCallInProgressEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallInProgressEvent(that); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallInProgressEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process call error event + * @private + * @param {string} error call control error + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallOpenErrorEvent = function(error) { + console.debug("WebRTCommCall:onPrivateCallConnectorCallOpenErrorEvent():error=" + error); + // Notify event to the listener + if (this.eventListener.onWebRTCommCallOpenErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallOpenErrorEvent(that, error); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallOpenErrorEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process call ringing event + * @private + * @param {string} callerPhoneNumber caller contact identifier (e.g. bob@sip.net) + * @param {string} callerDisplayName caller contact identifier (e.g. bob@sip.net) + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallRingingEvent = function(callerPhoneNumber, callerDisplayName, customHeaders) { + console.debug("WebRTCommCall:onPrivateCallConnectorCallRingingEvent():callerPhoneNumber=" + callerPhoneNumber); + console.debug("WebRTCommCall:onPrivateCallConnectorCallRingingEvent():callerDisplayName=" + callerDisplayName); + console.debug("WebRTCommCall:onPrivateCallConnectorCallRingingEvent():customHeaders=" + JSON.stringify(customHeaders)); + + // Notify the closed event to the listener + this.callerPhoneNumber = callerPhoneNumber; + this.callerDisplayName = callerDisplayName; + this.customHeaders = customHeaders; + if (this.eventListener.onWebRTCommCallRingingEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallRingingEvent(that); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallRingingEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process call ringing back event + * @private + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallRingingBackEvent = function() { + console.debug("WebRTCommCall:onPrivateCallConnectorCallRingingBackEvent()"); + // Notify the closed event to the listener + if (this.eventListener.onWebRTCommCallRingingBackEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallRingingBackEvent(that); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallRingingBackEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + + +/** + * Implementation of the PrivateCallConnector listener interface: process call closed event + * @private + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallClosedEvent = function() { + console.debug("WebRTCommCall:onPrivateCallConnectorCallClosedEvent()"); + this.connector = undefined; + // Force communication close + try { + this.close(true); + } catch (exception) {} +}; + + +/** + * Implementation of the PrivateCallConnector listener interface: process call hangup event + * @private + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallHangupEvent = function() { + console.debug("WebRTCommCall:onPrivateCallConnectorCallHangupEvent()"); + // Notify the closed event to the listener + if (this.eventListener.onWebRTCommCallHangupEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallHangupEvent(that); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallHangupEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + +/** + * Implementation of the PrivateCallConnector listener interface: process incoming call cancel event + * @private + */ +WebRTCommCall.prototype.onPrivateCallConnectorCallCanceledEvent = function() { + console.debug("WebRTCommCall:onPrivateCallConnectorCallCanceledEvent()"); + // Notify the canceled event to the listener + if (this.eventListener.onWebRTCommCallCanceledEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallCanceledEvent(that); + } catch (exception) { + console.error("WebRTCommCall:onPrivateCallConnectorCallCanceledEvent(): catched exception in listener:" + exception); + } + }, 1); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: process RTCPeerConnection error event + * @private + * @param {string} error internal error + */ +WebRTCommCall.prototype.onRtcPeerConnectionErrorEvent = function(error) { + console.debug("WebRTCommCall:onRtcPeerConnectionErrorEvent(): error=" + error); + // Critical issue, notify the error and close properly the call + // Notify the error event to the listener + if (this.eventListener.onWebRTCommCallOpenErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallOpenErrorEvent(that, error); + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionErrorEvent(): catched exception in listener:" + exception); + } + }, 1); + } + + try { + this.close(true); + } catch (exception) {} +}; + + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {MediaStreamEvent} event RTCPeerConnection Event + */ +WebRTCommCall.prototype.onRtcPeerConnectionAddStreamEvent = function(event) { + console.debug("[PC]: onaddstream()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): event=" + event); + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): event.type=" + event.type); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): this.peerConnectionState=" + this.peerConnectionState); + this.remoteBundledAudioVideoMediaStream = event.stream; + // https://code.google.com/p/webrtcomm/issues/detail?id=22 Make sure to call WebRTCommCall on add stream event + if (this.eventListener.onWebRTCommCallOpenedEvent) { + var that = this; + setTimeout(function() { + try { + console.debug("WebRTCommCall:calling onWebRTCommCallOpenedEvent(): event=" + event); + that.eventListener.onWebRTCommCallOpenedEvent(that); + console.debug("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): creating DTMF Sender"); + if (that.peerConnection.createDTMFSender) { + if (that.configuration.localMediaStream !== null) { + var localAudioTrack = that.configuration.localMediaStream.getAudioTracks()[0]; + console.debug("[PC]: createDTMFSender()"); + that.dtmfSender = that.peerConnection.createDTMFSender(localAudioTrack); + //that.dtmfSender.ontonechange = dtmfOnToneChange; + console.debug('Created DTMFSender'); + } else { + console.debug('No local stream to create DTMF Sender'); + } + } else { + console.warn('RTCPeerConnection method createDTMFSender() is not supported by this browser, will fallback to SIP INFO DTMF.'); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): catched exception in listener:" + exception); + } + }, 1); + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionAddStreamEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {MediaStreamEvent} event RTCPeerConnection Event + */ +WebRTCommCall.prototype.onRtcPeerConnectionRemoveStreamEvent = function(event) { + console.debug("[PC]: onremovestream()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): this.peerConnectionState=" + this.peerConnectionState); + this.remoteBundledAudioVideoMediaStream = undefined; + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionRemoveStreamEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {RTCPeerConnectionIceEvent} rtcIceCandidateEvent RTCPeerConnection Event + */ +WebRTCommCall.prototype.onRtcPeerConnectionIceCandidateEvent = function(rtcIceCandidateEvent) { + console.debug("[PC]: onicecandidate()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): rtcIceCandidateEvent=" + JSON.stringify(rtcIceCandidateEvent.candidate)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): this.peerConnectionState=" + this.peerConnectionState); + if (this.peerConnection.signalingState !== 'closed') { + // Gathering complete is signalled here when rtcIceCandidateEvent.candidate is null + if (!rtcIceCandidateEvent.candidate && this.peerConnection.iceGatheringState === 'complete') { + if (this.peerConnectionState === 'preparing-offer') { + var sdpOfferString = this.peerConnection.localDescription.sdp; + var parsedSdpOffer = this.setRtcPeerConnectionLocalDescription(this.peerConnection.localDescription); + + // Apply modified SDP Offer + this.connector.invite(parsedSdpOffer); + this.peerConnectionState = 'offer-sent'; + } else if (this.peerConnectionState === 'preparing-answer') { + var sdpAnswerString = this.peerConnection.localDescription.sdp; + var parsedSdpAnswer = this.setRtcPeerConnectionLocalDescription(this.peerConnection.localDescription); + + this.connector.accept(parsedSdpAnswer); + this.peerConnectionState = 'established'; + } else if (this.peerConnectionState === 'established') { + // Why this last ice candidate event? + } else { + console.error("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): RTCPeerConnection bad state!" + this.peerConnectionState); + } + } + } else { + console.error("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): RTCPeerConnection closed!"); + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionIceCandidateEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(exception); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {RTCSessionDescription} sdpOffer RTCPeerConnection SDP offer event + */ +WebRTCommCall.prototype.onRtcPeerConnectionCreateOfferSuccessEvent = function(sdpOffer) { + console.debug("[PC]: onCreateOfferSuccessEvent()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): sdpOffer=" + JSON.stringify(sdpOffer)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): this.peerConnectionState=" + this.peerConnectionState); + + if (this.peerConnectionState === 'new') { + // Preparing offer. + var that = this; + this.peerConnectionState = 'preparing-offer'; + if (window.webkitRTCPeerConnection) { + this.setRtcPeerConnectionLocalDescription(sdpOffer); + } + + console.debug("[PC]: setLocalDescription()"); + this.peerConnection.setLocalDescription(sdpOffer, function() { + that.onRtcPeerConnectionSetLocalDescriptionSuccessEvent(); + }, function(error) { + that.onRtcPeerConnectionSetLocalDescriptionErrorEvent(error); + }); + } else { + console.error("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): RTCPeerConnection bad state!"); + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +WebRTCommCall.prototype.setRtcPeerConnectionLocalDescription = function(sdpOffer) { + var sdpOfferString = sdpOffer.sdp; + var sdpParser = new SDPParser(); + var parsedSdpOffer = sdpParser.parse(sdpOfferString); + + this.removeEmptyIceUfragPwdAttributes(parsedSdpOffer); + + // Check if offer is ok with the requested media constraints + /* + if (window.webkitRTCPeerConnection) { + if (this.configuration.videoMediaFlag === false) { + this.removeMediaDescription(parsedSdpOffer, "video"); + } + + if (this.configuration.audioMediaFlag === false) { + this.removeMediaDescription(parsedSdpOffer, "audio"); + } + } + */ + + if (this.configuration.audioCodecsFilter || this.configuration.videoCodecsFilter || this.configuration.opusFmtpCodecsParameters) { + try { + // Apply audio/video codecs filter to RTCPeerConnection SDP offer to + this.applyConfiguredCodecFilterOnSessionDescription(parsedSdpOffer); + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): configured codec filtering has failded, use inital RTCPeerConnection SDP offer"); + } + } + + // Check if offer is ok with the requested RTCPeerConnection constraints + if (this.webRTCommClient.configuration.RTCPeerConnection.forceTurnMediaRelay === true) { + this.forceTurnMediaRelay(parsedSdpOffer); + } + // Allow patching of chrome ice-options for interconnect with Mobicents Media Server, commented for now but to be made configurable + // this.patchChromeIce(parsedSdpOffer, "ice-options"); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferSuccessEvent(): parsedSdpOffer=" + parsedSdpOffer); + + // Apply modified SDP Offer + sdpOffer.sdp = parsedSdpOffer; + this.peerConnectionLocalDescription = sdpOffer; + + return parsedSdpOffer; +} + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {object} error RTCPeerConnection SDP offer error event + */ +WebRTCommCall.prototype.onRtcPeerConnectionCreateOfferErrorEvent = function(error) { + console.debug("[PC]: onCreateOfferErrorEvent()"); + try { + console.error("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent():error=" + JSON.stringify(error)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent(): this.peerConnectionState=" + this.peerConnectionState); + throw "WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent():error=" + error; + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionCreateOfferErrorEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + */ +WebRTCommCall.prototype.onRtcPeerConnectionSetLocalDescriptionSuccessEvent = function() { + console.debug("[PC]: onSetLocalDescriptionSuccess()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent():" + JSON.stringify(this.peerConnection)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent(): this.peerConnectionState=" + this.peerConnectionState); + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionSuccessEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {object} error RTCPeerConnection SDP offer error event + */ +WebRTCommCall.prototype.onRtcPeerConnectionSetLocalDescriptionErrorEvent = function(error) { + console.debug("[PC]: onSetLocalDescriptionError()"); + try { + console.error("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent():error=" + JSON.stringify(error)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent(): this.peerConnectionState=" + this.peerConnectionState); + throw "WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent():error=" + error; + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionSetLocalDescriptionErrorEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {RTCSessionDescription} answer RTCPeerConnection SDP answer event + */ +WebRTCommCall.prototype.onRtcPeerConnectionCreateAnswerSuccessEvent = function(sdpAnswser) { + console.debug("[PC]: onCreateAnswerSuccess()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent():answer=" + JSON.stringify(sdpAnswser)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): this.peerConnectionState=" + this.peerConnectionState); + + if (this.peerConnectionState === 'offer-received') { + // Prepare answer. + var that = this; + this.peerConnectionState = 'preparing-answer'; + var sdpAnswerString = sdpAnswser.sdp; + var sdpParser = new SDPParser(); + var parsedSdpAnswer = sdpParser.parse(sdpAnswerString); + + // Check if offer is ok with the requested media constraints + // Can not remove/add SDP m lines + + if (this.configuration.audioCodecsFilter || this.configuration.videoCodecsFilter || this.configuration.opusFmtpCodecsParameters) { + try { + // Apply audio/video codecs filter to RTCPeerConnection SDP offer to + this.applyConfiguredCodecFilterOnSessionDescription(parsedSdpAnswer); + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): configured codec filtering has failded, use inital RTCPeerConnection SDP offer"); + } + } + // Allow patching of chrome ice-options for interconnect with Mobicents Media Server, commented for now but to be made configurable + // this.patchChromeIce(parsedSdpOffer, "ice-options"); + + sdpAnswser.sdp = parsedSdpAnswer; + this.peerConnectionLocalDescription = parsedSdpAnswer; + console.debug("[PC]: setLocalDescription()"); + this.peerConnection.setLocalDescription(sdpAnswser, function() { + that.onRtcPeerConnectionSetLocalDescriptionSuccessEvent(); + }, function(error) { + that.onRtcPeerConnectionSetLocalDescriptionErrorEvent(error); + }); + } else { + console.error("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): RTCPeerConnection bad state!"); + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionCreateAnswerSuccessEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * Implementation of the RTCPeerConnection listener interface: handle RTCPeerConnection state machine + * @private + * @param {String} error SDP error + */ +WebRTCommCall.prototype.onRtcPeerConnectionCreateAnswerErrorEvent = function(error) { + console.debug("[PC]: onCreateAnswerError()"); + console.error("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent():error=" + JSON.stringify(error)); + try { + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent(): this.peerConnectionState=" + this.peerConnectionState); + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionCreateAnswerErrorEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + */ +WebRTCommCall.prototype.onRtcPeerConnectionSetRemoteDescriptionSuccessEvent = function() { + console.debug("[PC]: onSetRemoteDescriptionSuccess()"); + try { + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent()"); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): this.peerConnectionState=" + this.peerConnectionState); + + if (this.peerConnectionState === 'answer-received') { + this.peerConnectionState = 'established'; + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): this.peerConnectionState=" + this.peerConnectionState); + } else if (this.peerConnectionState === 'offer-received') { + var that = this; + if (window.webkitRTCPeerConnection) { + var sdpConstraints = { + mandatory: { + OfferToReceiveAudio: this.configuration.audioMediaFlag, + OfferToReceiveVideo: this.configuration.videoMediaFlag + }, + optional: [] + }; + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent():sdpConstraints=" + JSON.stringify(sdpConstraints)); + console.debug("[PC]: createAnswer()"); + this.peerConnection.createAnswer(function(answer) { + that.onRtcPeerConnectionCreateAnswerSuccessEvent(answer); + }, function(error) { + that.onRtcPeerConnectionCreateAnswerErrorEvent(error); + }, sdpConstraints); + } else if (window.mozRTCPeerConnection) { + var sdpConstraints = { + offerToReceiveAudio: this.configuration.audioMediaFlag, + offerToReceiveVideo: this.configuration.videoMediaFlag, + mozDontOfferDataChannel: !this.configuration.messageMediaFlag + }; + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent():sdpConstraints=" + JSON.stringify(sdpConstraints)); + console.debug("[PC]: createAnswer()"); + this.peerConnection.createAnswer(function(answer) { + that.onRtcPeerConnectionCreateAnswerSuccessEvent(answer); + }, function(error) { + that.onRtcPeerConnectionCreateAnswerErrorEvent(error); + }, sdpConstraints); + } + } else { + console.error("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): RTCPeerConnection bad state!"); + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): event ignored"); + } + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionSuccessEvent(): catched exception, exception:" + exception); + this.onRtcPeerConnectionErrorEvent(); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {String} error SDP error + */ +WebRTCommCall.prototype.onRtcPeerConnectionSetRemoteDescriptionErrorEvent = function(error) { + console.debug("[PC]: onSetRemoteDescriptionError()"); + try { + console.error("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent():error=" + JSON.stringify(error)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent(): this.peerConnectionState=" + this.peerConnectionState); + throw "WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent():error=" + error; + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionSetRemoteDescriptionErrorEvent(): event ignored"); + } + } catch (exception) { + this.onRtcPeerConnectionErrorEvent(error); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {Event} event RTCPeerConnection open event + */ +WebRTCommCall.prototype.onRtcPeerConnectionOpenEvent = function(event) { + console.debug("[PC]: onopen()"); + console.debug("WebRTCommCall:onRtcPeerConnectionOpenEvent(): event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionOpenEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionOpenEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionOpenEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionOpenEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionOpenEvent(): this.peerConnectionState=" + this.peerConnectionState); + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionOpenEvent(): event ignored"); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {Event} event RTCPeerConnection open event + */ +WebRTCommCall.prototype.onRtcPeerConnectionSignalingStateChangeEvent = function(event) { + console.debug("[PC]: onsignalingstatechange()"); + console.debug("WebRTCommCall:onRtcPeerConnectionSignalingStateChangeEvent(): event=" + JSON.stringify(event)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionSignalingStateChangeEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionSignalingStateChangeEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionSignalingStateChangeEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionSignalingStateChangeEvent(): this.peerConnectionState=" + this.peerConnectionState); + if (this.peerConnection && this.peerConnection.signalingState === 'closed') + this.peerConnection = null; + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionSignalingStateChangeEvent(): event ignored"); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {Event} event RTCPeerConnection ICE negociation Needed event + */ +WebRTCommCall.prototype.onRtcPeerConnectionIceNegotiationNeededEvent = function(event) { + console.debug("WebRTCommCall:onRtcPeerConnectionIceNegotiationNeededEvent():event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionIceNegotiationNeededEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceNegotiationNeededEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceNegotiationNeededEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceNegotiationNeededEvent(): this.peerConnectionState=" + this.peerConnectionState); + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionIceNegotiationNeededEvent(): event ignored"); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {Event} event RTCPeerConnection open event + */ +WebRTCommCall.prototype.onRtcPeerConnectionIceConnectionStateChangeEvent = function(event) { + console.debug("[PC]: oniceconnectionstatechange()"); + console.debug("WebRTCommCall:onRtcPeerConnectionIceConnectionStateChangeEvent():event=" + JSON.stringify(event)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionIceConnectionStateChangeEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceConnectionStateChangeEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceConnectionStateChangeEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionIceConnectionStateChangeEvent(): this.peerConnectionState=" + this.peerConnectionState); + if (this.peerConnection.iceConnectionState == 'failed') { + error = 'Media path is lost due to connectivity issues; call has been hung up'; + console.error("WebRTCommCall:onRtcPeerConnectionIceConnectionStateChangeEvent(): " + error); + + // Error, notify the error and close properly the call + // Notify the error event to the listener + if (this.eventListener.onWebRTCommCallErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommCallErrorEvent(that, error); + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionErrorEvent(): catched exception in listener:" + exception); + } + }, 1); + } + + // close the call since media has failed + try { + this.close(true); + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionErrorEvent(): catched exception in listener:" + exception); + } + } + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {Event} event RTCPeerConnection identity event + */ +WebRTCommCall.prototype.onRtcPeerConnectionIdentityResultEvent = function(event) { + console.debug("WebRTCommCall:onRtcPeerConnectionIdentityResultEvent():event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionIdentityResultEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionIdentityResultEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionIdentityResultEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionIdentityResultEvent(): this.peerConnectionState=" + this.peerConnectionState); + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionIdentityResultEvent(): event ignored"); + } +}; + +/** + * RTCPeerConnection listener implementation + * @private + * @param {Event} event RTCPeerConnection data channel event + */ +WebRTCommCall.prototype.onRtcPeerConnectionOnMessageChannelEvent = function(event) { + console.debug("[PC]: ondatachannel()"); + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent():event=" + JSON.stringify(event)); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.peerConnectionState=" + this.peerConnectionState); + this.messageChannel = event.channel; + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.messageChannel.label=" + this.messageChannel.label); + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.messageChannel.reliable=" + this.messageChannel.reliable); + console.debug("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): this.messageChannel.binaryType=" + this.messageChannel.binaryType); + var that = this; + this.messageChannel.onopen = function(event) { + that.onRtcPeerConnectionMessageChannelOpenEvent(event); + }; + this.messageChannel.onclose = function(event) { + that.onRtcPeerConnectionMessageChannelClose(event); + }; + this.messageChannel.onerror = function(event) { + that.onRtcPeerConnectionMessageChannelErrorEvent(event); + }; + this.messageChannel.onmessage = function(event) { + that.onRtcPeerConnectionMessageChannelMessageEvent(event); + }; + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionOnMessageChannelEvent(): event ignored"); + } +}; + +WebRTCommCall.prototype.onRtcPeerConnectionMessageChannelOpenEvent = function(event) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent():event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): this.peerConnectionState=" + this.peerConnectionState); + if (this.messageChannel) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): this.messageChannel.readyState=" + this.messageChannel.readyState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): this.messageChannel.binaryType=" + this.messageChannel.bufferedAmmount); + if (this.eventListener.onWebRTCommDataMessageChannelOnOpenEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommDataMessageChannelOnOpenEvent(); + } catch (exception) { + console.error("WebRTCommCall:onWebRTCommDataMessageChannelOnOpenEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionMessageChannelOpenEvent(): event ignored"); + } +}; + +WebRTCommCall.prototype.onRtcPeerConnectionMessageChannelClose = function(event) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose():event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): this.peerConnectionState=" + this.peerConnectionState); + if (this.messageChannel) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): this.messageChannel.readyState=" + this.messageChannel.readyState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): this.messageChannel.binaryType=" + this.messageChannel.bufferedAmmount); + if (this.eventListener.onWebRTCommDataMessageChannelOnCloseEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommDataMessageChannelOnCloseEvent(); + } catch (exception) { + console.error("WebRTCommCall:onWebRTCommDataMessageChannelOnCloseEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionMessageChannelClose(): event ignored"); + } +}; + +WebRTCommCall.prototype.onRtcPeerConnectionMessageChannelErrorEvent = function(event) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent():event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): this.peerConnectionState=" + this.peerConnectionState); + if (this.messageChannel) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): this.messageChannel.readyState=" + this.messageChannel.readyState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): this.messageChannel.binaryType=" + this.messageChannel.bufferedAmmount); + if (this.eventListener.onWebRTCommDataMessageChannelOnErrorEvent) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommDataMessageChannelOnErrorEvent(); + } catch (exception) { + console.error("WebRTCommCall:onWebRTCommDataMessageChannelOnErrorEvent(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionMessageChannelErrorEvent(): event ignored"); + } +}; + +WebRTCommCall.prototype.onRtcPeerConnectionMessageChannelMessageEvent = function(event) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent():event=" + event); + if (this.peerConnection) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): this.peerConnection.signalingState=" + this.peerConnection.signalingState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): this.peerConnection.iceGatheringState=" + this.peerConnection.iceGatheringState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): this.peerConnection.iceConnectionState=" + this.peerConnection.iceConnectionState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): this.peerConnectionState=" + this.peerConnectionState); + if (this.messageChannel) { + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): this.messageChannel.readyState=" + this.messageChannel.readyState); + console.debug("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): this.messageChannel.binaryType=" + this.messageChannel.bufferedAmmount); + if (this.eventListener.onWebRTCommDataMessageReceivedEvent) { + // Build WebRTCommMessage + var newWebRTCommDataMessage = new WebRTCommDataMessage(this.webRTCommClient, this); + newWebRTCommDataMessage.content = event.data; + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommDataMessageReceivedEvent(newWebRTCommDataMessage); + } catch (exception) { + console.error("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): catched exception in listener:" + exception); + } + }, 1); + } + } + } else { + console.warn("WebRTCommCall:onRtcPeerConnectionMessageChannelMessageEvent(): event ignored"); + } +}; + +/** + * Modifiy SDP based on configured codec filter + * @private + * @param {SessionDescription} sessionDescription JAIN (gov.nist.sdp) SDP offer object + */ +WebRTCommCall.prototype.applyConfiguredCodecFilterOnSessionDescription = function(sessionDescription) { + if (sessionDescription instanceof SessionDescription) { + try { + console.debug("WebRTCommCall:applyConfiguredCodecFilterOnSessionDescription(): sessionDescription=" + sessionDescription); + // Deep copy the media descriptions + var mediaDescriptions = sessionDescription.getMediaDescriptions(false); + for (var i = 0; i < mediaDescriptions.length; i++) { + var mediaDescription = mediaDescriptions[i]; + var mediaField = mediaDescription.getMedia(); + var mediaType = mediaField.getType(); + if (mediaType === "audio") { + if (this.configuration.audioCodecsFilter) { + var offeredAudioCodecs = this.getOfferedCodecsInMediaDescription(mediaDescription); + // Filter offered codec first + var splitAudioCodecsFilters = (this.configuration.audioCodecsFilter).split(","); + this.applyCodecFiltersOnOfferedCodecs(offeredAudioCodecs, splitAudioCodecsFilters); + // Apply modification on audio media description + this.updateMediaDescription(mediaDescription, offeredAudioCodecs, splitAudioCodecsFilters); + } + + // Add OPUS parameter if required + if (this.configuration.opusFmtpCodecsParameters) { + this.updateOpusMediaDescription(mediaDescription, this.configuration.opusFmtpCodecsParameters); + } + } else if (mediaType === "video" && this.configuration.videoCodecsFilter) { + var offeredVideoCodecs = this.getOfferedCodecsInMediaDescription(mediaDescription); + // Filter offered codec + var splitVideoCodecFilter = (this.configuration.videoCodecsFilter).split(","); + this.applyCodecFiltersOnOfferedCodecs(offeredVideoCodecs, splitVideoCodecFilter); + // Apply modification on video media description + this.updateMediaDescription(mediaDescription, offeredVideoCodecs, splitVideoCodecFilter); + } + } + } catch (exception) { + console.error("WebRTCommCall:applyConfiguredCodecFilterOnSessionDescription(): catched exception, exception:" + exception); + throw exception; + } + } else { + throw "WebRTCommCall:applyConfiguredCodecFilterOnSessionDescription(): bad arguments" + } +}; + +/** + * Get offered codecs in media description + * @private + * @param {MediaDescription} mediaDescription JAIN (gov.nist.sdp) MediaDescription object + * @return offeredCodec JSON object { "0":"PCMU", "111":"OPUS", .....} + */ +WebRTCommCall.prototype.getOfferedCodecsInMediaDescription = function(mediaDescription) { + console.debug("WebRTCommCall:getOfferedCodecsInMediaDescription()"); + if (mediaDescription instanceof MediaDescription) { + var mediaFormats = mediaDescription.getMedia().getFormats(false); + var foundCodecs = {}; + + // Set static payload type and codec name + for (var j = 0; j < mediaFormats.length; j++) { + var payloadType = mediaFormats[j]; + console.debug("WebRTCommCall:getOfferedCodecsInMediaDescription(): payloadType=" + payloadType); + console.debug("WebRTCommCall:getOfferedCodecsInMediaDescription(): this.codecNames[payloadType]=" + this.codecNames[payloadType]); + foundCodecs[payloadType] = this.codecNames[payloadType]; + } + + // Set dynamic payload type and codec name + var attributFields = mediaDescription.getAttributes(); + for (var k = 0; k < attributFields.length; k++) { + var attributField = attributFields[k]; + if (attributField.getName() === "rtpmap") { + try { + var rtpmapValue = attributField.getValue(); + var splitRtpmapValue = rtpmapValue.split(" "); + var payloadType = splitRtpmapValue[0]; + var codecInfo = splitRtpmapValue[1]; + var splittedCodecInfo = codecInfo.split("/"); + var codecName = splittedCodecInfo[0]; + foundCodecs[payloadType] = codecName.toUpperCase(); + console.debug("WebRTCommCall:getOfferedCodecsInMediaDescription(): payloadType=" + payloadType); + console.debug("WebRTCommCall:getOfferedCodecsInMediaDescription(): codecName=" + codecName); + } catch (exception) { + console.error("WebRTCommCall:getOfferedCodecsInMediaDescription(): rtpmap/fmtp format not supported"); + } + } + } + return foundCodecs; + } else { + throw "WebRTCommCall:getOfferedCodecsInMediaDescription(): bad arguments" + } +}; + +/** + * Get offered codec list + * @private + * @param {JSON object} foundCodecs + * @param {Array} codecFilters + */ +WebRTCommCall.prototype.applyCodecFiltersOnOfferedCodecs = function(foundCodecs, codecFilters) { + console.debug("WebRTCommCall:applyCodecFiltersOnOfferedCodecs()"); + if (typeof(foundCodecs) === 'object' && codecFilters instanceof Array) { + for (var offeredMediaCodecPayloadType in foundCodecs) { + var filteredFlag = false; + for (var i = 0; i < codecFilters.length; i++) { + if (foundCodecs[offeredMediaCodecPayloadType] === codecFilters[i]) { + filteredFlag = true; + break; + } + } + if (filteredFlag === false) { + delete(foundCodecs[offeredMediaCodecPayloadType]); + } + } + } else { + throw "WebRTCommCall:applyCodecFiltersOnOfferedCodecs(): bad arguments" + } +}; + +/** + * Update offered media description avec configured filters + * @private + * @param {MediaDescription} mediaDescription JAIN (gov.nist.sdp) MediaDescription object + * @param {JSON object} filteredCodecs + * @param {Array} codecFilters + */ +WebRTCommCall.prototype.updateMediaDescription = function(mediaDescription, filteredCodecs, codecFilters) { + console.debug("WebRTCommCall:updateMediaDescription()"); + if (mediaDescription instanceof MediaDescription && typeof(filteredCodecs) === 'object' && codecFilters instanceof Array) { + // Build new media field format lis + var newFormatListArray = new Array(); + for (var i = 0; i < codecFilters.length; i++) { + for (var offeredCodecPayloadType in filteredCodecs) { + if (filteredCodecs[offeredCodecPayloadType] === codecFilters[i]) { + newFormatListArray.push(offeredCodecPayloadType); + break; + } + } + } + mediaDescription.getMedia().setFormats(newFormatListArray); + // Remove obsolte rtpmap attributs + var newAttributeFieldArray = new Array(); + var attributFields = mediaDescription.getAttributes(); + for (var k = 0; k < attributFields.length; k++) { + var attributField = attributFields[k]; + if (attributField.getName() === "rtpmap" || attributField.getName() === "fmtp") { + try { + var rtpmapValue = attributField.getValue(); + var splitedRtpmapValue = rtpmapValue.split(" "); + var payloadType = splitedRtpmapValue[0]; + if (filteredCodecs[payloadType] !== undefined) + newAttributeFieldArray.push(attributField); + } catch (exception) { + console.error("WebRTCommCall:updateMediaDescription(): rtpmap/fmtp format not supported"); + } + } else + newAttributeFieldArray.push(attributField); + } + mediaDescription.setAttributes(newAttributeFieldArray); + } else { + throw "WebRTCommCall:updateMediaDescription(): bad arguments" + } +}; + +/** + * Update offered OPUS media description avec required FMTP parameters + * @private + * @param {MediaDescription} mediaDescription JAIN (gov.nist.sdp) MediaDescription object + * @param {string} opusMediaFmtpParameters FMTP OPUS parameters + */ +WebRTCommCall.prototype.updateOpusMediaDescription = function(mediaDescription, opusMediaFmtpParameters) { + console.debug("WebRTCommCall:updateOpusMediaDescription()"); + if (mediaDescription instanceof MediaDescription && typeof(opusMediaFmtpParameters) === 'string') { + // Find OPUS payload Type + var opusPayloadType = undefined; + var attributFields = mediaDescription.getAttributes(); + for (var i = 0; i < attributFields.length; i++) { + var attributField = attributFields[i]; + if (attributField.getName() === "rtpmap") { + try { + var rtpmapValue = attributField.getValue().toLowerCase(); + if (rtpmapValue.indexOf("opus") >= 0) { + var splitedRtpmapValue = rtpmapValue.split(" "); + opusPayloadType = splitedRtpmapValue[0]; + break; + } + } catch (exception) { + console.error("WebRTCommCall:updateMediaDescription(): rtpmap/fmtp format not supported"); + } + } + } + + if (opusPayloadType) { + console.debug("WebRTCommCall:updateOpusMediaDescription():opusPayloadType=" + opusPayloadType); + // Update FMTP OPUS SDP parameter + for (var j = 0; j < attributFields.length; j++) { + var attributField = attributFields[j]; + if (attributField.getName() === "fmtp") { + try { + var fmtpValue = attributField.getValue(); + var splitedFmtpValue = rtpmapValue.split(" "); + var payloadType = splitedFmtpValue[0]; + if (opusPayloadType === payloadType) { + attributField.setValue(fmtpValue + " " + opusMediaFmtpParameters); + console.debug("WebRTCommCall:updateOpusMediaDescription():fmtp=" + attributField.getValue()); + } + } catch (exception) { + console.error("WebRTCommCall:updateMediaDescription(): rtpmap/fmtp format not supported"); + } + } + } + } + } else { + throw "WebRTCommCall:updateMediaDescription(): bad arguments" + } +}; + +/** + * Modifiy SDP based on configured codec filter + * @private + * @param {SessionDescription} sessionDescription JAIN (gov.nist.sdp) SDP offer object + * @param {String} mediaTypeToRemove audi/video + */ +WebRTCommCall.prototype.patchChromeIce = function(sessionDescription, attributeToCheck) { + console.debug("WebRTCommCall:patchChromeIce()"); + if (sessionDescription instanceof SessionDescription) { + try { + var otherAttributes = sessionDescription.getAttributes(false); + if (otherAttributes != null) { + for (var i = 0; i < otherAttributes.length; i++) { + var attributField = otherAttributes[i]; + if (attributField.getName() === attributeToCheck) { + console.debug("WebRTCommCall:patchChromeIce(), found ice-options session attribute trying to patch"); + try { + var rtpmapValue = attributField.getValue().toLowerCase(); + if (rtpmapValue.indexOf("google-ice") >= 0) { + console.debug("WebRTCommCall:patchChromeIce(), found google-ice session attribute trying to patch"); + //attributField.setValue("trickle"); + attributFields.remove(i); + break; + } + } catch (exception) { + console.error("WebRTCommCall:updateMediaDescription(): rtpmap/fmtp format not supported"); + } + } + } + } + var mediaDescriptions = sessionDescription.getMediaDescriptions(false); + for (var i = 0; i < mediaDescriptions.length; i++) { + var attributFields = mediaDescriptions[i].getAttributes(); + for (var j = 0; j < attributFields.length; j++) { + var attributField = attributFields[j]; + if (attributField.getName() === attributeToCheck) { + console.debug("WebRTCommCall:patchChromeIce(), found ice-options media attribute trying to patch"); + try { + var rtpmapValue = attributField.getValue().toLowerCase(); + if (rtpmapValue.indexOf("google-ice") >= 0) { + console.debug("WebRTCommCall:patchChromeIce(), found google-ice mediajattribute trying to patch"); + //attributField.setValue("trickle"); + attributFields.remove(j); + break; + } + } catch (exception) { + console.error("WebRTCommCall:updateMediaDescription(): rtpmap/fmtp format not supported"); + } + } + } + } + } catch (exception) { + console.error("WebRTCommCall:patchChromeIce(): catched exception, exception:" + exception); + throw exception; + } + } else { + throw "WebRTCommCall:patchChromeIce(): bad arguments" + } +}; + +/** + * If SDP attributes ice-ufrag and or ice-pwd exist in the SDP but are empty, they need to be removed + * @private + * @param {SessionDescription} sessionDescription JAIN (gov.nist.sdp) SDP offer object + */ +WebRTCommCall.prototype.removeEmptyIceUfragPwdAttributes = function(sessionDescription ) { + // Check if ice-ufrag and pwd are empty and if so remove + var mediaDescriptions = sessionDescription.getMediaDescriptions(false); + for (var i = 0; i < mediaDescriptions.length; i++) { + var newAttributeFieldArray = new Array(); + var attributeFields = mediaDescriptions[i].getAttributes(); + for (var k = 0; k < attributeFields.length; k++) { + var attributeField = attributeFields[k]; + if ((attributeField.getName() === "ice-ufrag" && !attributeField.getValue()) || + (attributeField.getName() === "ice-pwd" && !attributeField.getValue())) { + console.warn("WebRTCommCall:setRtcPeerConnectionLocalDescription(): found empty ice-ufrag/ice-pwd; removing them"); + } + else { + newAttributeFieldArray.push(attributeField); + } + } + mediaDescriptions[i].setAttributes(newAttributeFieldArray); + } +} + +/** + * Modifiy SDP based on configured codec filter + * @private + * @param {SessionDescription} sessionDescription JAIN (gov.nist.sdp) SDP offer object + * @param {String} mediaTypeToRemove audi/video + */ +WebRTCommCall.prototype.removeMediaDescription = function(sessionDescription, mediaTypeToRemove) { + console.debug("WebRTCommCall:removeMediaDescription()"); + if (sessionDescription instanceof SessionDescription) { + try { + /* No need to remove media descriptions, it's not properly handled by PeerConnection + var mediaDescriptions = sessionDescription.getMediaDescriptions(false); + for (var i = 0; i < mediaDescriptions.length; i++) { + var mediaDescription = mediaDescriptions[i]; + var mediaField = mediaDescription.getMedia(); + var mediaType = mediaField.getType(); + if (mediaType === mediaTypeToRemove) { + mediaDescriptions.remove(i); + break; + } + } + */ + + if (window.mozRTCPeerConnection) { + var attributes = sessionDescription.getAttributes(false); + for (var i = 0; i < attributes.length; i++) { + var attribute = attributes[i]; + var attributeValue = attribute.getValue(); + if ("BUNDLE sdparta_0 sdparta_1" === attributeValue) { + if ("video" === mediaTypeToRemove) { + attribute.setValue("BUNDLE sdparta_0"); + break; + } + if ("audio" === mediaTypeToRemove) { + attribute.setValue("BUNDLE sdparta_1"); + break; + } + } + } + } + } catch (exception) { + console.error("WebRTCommCall:removeMediaDescription(): catched exception, exception:" + exception); + throw exception; + } + } else { + throw "WebRTCommCall:removeMediaDescription(): bad arguments" + } +}; + +/** + * Modifiy SDP, remove non "relay" ICE candidates + * @private + * @param {SessionDescription} sessionDescription JAIN (gov.nist.sdp) SDP offer object + */ +WebRTCommCall.prototype.forceTurnMediaRelay = function(sessionDescription) { + console.debug("WebRTCommCall:forceTurnMediaRelay()"); + if (sessionDescription instanceof SessionDescription) { + try { + var mediaDescriptions = sessionDescription.getMediaDescriptions(false); + for (var i = 0; i < mediaDescriptions.length; i++) { + var mediaDescription = mediaDescriptions[i]; + var newAttributeFieldArray = new Array(); + var attributFields = mediaDescription.getAttributes(); + for (var k = 0; k < attributFields.length; k++) { + var attributField = attributFields[k]; + if (attributField.getName() === "candidate") { + var candidateValue = attributField.getValue(); + var isRelayCandidate = candidateValue.indexOf("typ relay") > 0; + if (isRelayCandidate) { + newAttributeFieldArray.push(attributField); + } + } else + newAttributeFieldArray.push(attributField); + } + mediaDescription.setAttributes(newAttributeFieldArray); + } + } catch (exception) { + console.error("WebRTCommCall:forceTurnMediaRelay(): catched exception, exception:" + exception); + throw exception; + } + } else { + throw "WebRTCommCall:forceTurnMediaRelay(): bad arguments" + } +}; +/** + * @class WebRTCommMessage + * @classdesc Implements WebRTComm message + * @constructor + * @public + * @param {WebRTCommClient} webRTCommClient WebRTComm client owner + * @param {WebRTCommCall} webRTCommCall WebRTComm call owner + * @author Laurent STRULLU (laurent.strullu@orange.com) + */ +WebRTCommMessage = function(webRTCommClient, webRTCommCall) { + console.debug("WebRTCommMessage:WebRTCommMessage()"); + if ((webRTCommClient instanceof WebRTCommClient) || (webRTCommCall instanceof WebRTCommCall)) { + this.id = undefined; + this.webRTCommClient = webRTCommClient; + this.webRTCommCall = webRTCommCall; + this.connector = this.webRTCommClient.connector.createPrivateSessionConnector(this); + this.text = undefined; + this.from = undefined; + this.to = undefined; + } else { + throw "WebRTCommMessage:WebRTCommMessage(): bad arguments" + } +}; + + +/** + * Get message id + * @public + * @returns {String} id + */ +WebRTCommMessage.prototype.getId = function() { + return this.connector.getId(); +}; + +/** + * Get message sender identity + * @public + * @returns {String} from + */ +WebRTCommMessage.prototype.getFrom = function() { + return this.from; +}; + +/** + * Get message recever identity + * @public + * @returns {String} to + */ +WebRTCommMessage.prototype.getTo = function() { + return this.to; +}; + +/** + * Get message + * @public + * @returns {String} message + */ +WebRTCommMessage.prototype.getText = function() { + return this.text; +}; + +/** + * Get related WebRTCommCall + * @public + * @returns {WebRTCommCall} WebRTCommCall + */ +WebRTCommMessage.prototype.getLinkedWebRTCommCall = function() { + return this.webRTCommCall; +};/** + * @class WebRTCommClient + * @classdesc Main class of the WebRTComm Framework providing high level communication service: call and be call + * @constructor + * @public + * @param {object} eventListener event listener object implementing WebRTCommClient and WebRTCommCall listener interface + */ +WebRTCommClient = function(eventListener) { + if (typeof eventListener === 'object') { + this.id = "WebRTCommClient" + Math.floor(Math.random() * 2147483648); + console.debug("WebRTCommClient:WebRTCommClient():this.id=" + this.id); + this.eventListener = eventListener; + this.configuration = undefined; + this.connector = undefined; + this.closePendingFlag = false; + } else { + throw "WebRTCommClient:WebRTCommClient(): bad arguments" + } +}; + +/** + * SIP call control protocol mode + * @public + * @constant + */ +WebRTCommClient.prototype.SIP = "SIP"; + + +/** + * Get opened/closed status + * @public + * @returns {boolean} true if opened, false if closed + */ +WebRTCommClient.prototype.isOpened = function() { + if (this.connector) + return this.connector.isOpened(); + else + return false; +}; + +/** + * Get client configuration + * @public + * @returns {object} configuration + */ +WebRTCommClient.prototype.getConfiguration = function() { + return this.configuration; +}; + +/** + * Open the WebRTC communication client, asynchronous action, opened or error event are notified to the eventListener + * @public + * @param {object} configuration WebRTC communication client configuration
+ *

Client configuration sample:
+ * {
+ * communicationMode:WebRTCommClient.prototype.SIP,
+ * sip: {,
+ * sipUriContactParameters:undefined,
+ * sipUserAgent:"WebRTCommTestWebApp/0.0.1",
+ * sipUserAgentCapabilities=undefined,
+ * sipOutboundProxy:"ws://localhost:5082",
+ * sipDomain:"sip.net",
+ * sipUserName:"alice",
+ * sipLogin:"alice@sip.net,
+ * sipPassword:"1234567890",
+ * sipRegisterMode:true,
+ * }
+ * RTCPeerConnection: {,
+ * + * }
+ * }
+ *

+ * @throw {String} Exception "bad argument, check API documentation" + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + * @throw {String} Exception [internal error] + */ +WebRTCommClient.prototype.open = function(configuration) { + if (typeof(configuration) === 'object') { + if (this.isOpened() === false) { + if (this.checkConfiguration(configuration) === true) { + this.configuration = configuration; + if (configuration.communicationMode === WebRTCommClient.prototype.SIP) { + this.connector = new PrivateJainSipClientConnector(this); + this.connector.open(this.configuration.sip); + } + } else { + console.error("WebRTCommClient:open(): bad configuration"); + throw "WebRTCommClient:open(): bad configuration"; + } + } else { + console.error("WebRTCommClient:open(): bad state, unauthorized action"); + throw "WebRTCommClient:open(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommClient:open(): bad argument, check API documentation"); + throw "WebRTCommClient:open(): bad argument, check API documentation" + } +}; + +/** + * Close the WebRTC communication client, asynchronous action, closed event is notified to the eventListener + * @public + * @throw {String} Exception "bad argument, check API documentation" + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + */ +WebRTCommClient.prototype.close = function() { + console.debug("WebRTCommClient:close()"); + if (this.isOpened()) { + try { + this.closePendingFlag = true; + this.connector.close(); + } catch (exception) { + console.error("WebRTCommClient:close(): catched exception:" + exception); + // Force notification of closed event to listener + this.closePendingFlag = false; + this.connector = undefined; + if (this.eventListener.onWebRTCommClientClosedEvent !== undefined) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommClientClosedEvent(that); + } catch (exception) { + console.error("WebRTCommClient:onWebRTCommClientClosed(): catched exception in event listener:" + exception); + } + }, 1); + } + } + } +}; + + + +/** + * Send a short text message using transport (e.g SIP) implemented by the connector + * @public + * @param {String} to destination identifier (Tel URI, SIP URI: sip:bob@sip.net) + * @param {String} text Message to send
+ * @throw {String} Exception "bad argument, check API documentation" + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + * @returns {WebRTCommMessage} new created WebRTCommMessage object + */ +WebRTCommClient.prototype.sendMessage = function(to, text) { + try { + console.debug("WebRTCommClient:sendMessage(): to=" + to); + console.debug("WebRTCommClient:sendMessage(): text=" + text); + if (this.isOpened()) { + var newWebRTCommMessage = new WebRTCommMessage(this, undefined); + newWebRTCommMessage.to = to; + newWebRTCommMessage.text = text; + newWebRTCommMessage.connector.send(); + return newWebRTCommMessage; + } else { + console.error("WebRTCommClient:sendMessage(): bad state, unauthorized action"); + throw "WebRTCommClient:sendMessage(): bad state, unauthorized action"; + } + } catch (exception) { + console.error("WebRTCommClient:sendMessage(): catched exception:" + exception); + throw "WebRTCommClient:sendMessage(): catched exception:" + exception; + } +}; + +/** + * Request a WebRTC communication, asynchronous action, call events are notified to the eventListener + * @public + * @param {string} calleePhoneNumber Callee contact identifier (Tel URI, SIP URI: sip:bob@sip.net) + * @param {object} callConfiguration Communication configuration
+ *

Communication configuration sample:
+ * {
+ * displayName:alice,
+ * localMediaStream: [LocalMediaStream],
+ * audioMediaFlag:true,
+ * videoMediaFlag:false,
+ * dataMediaFlag:false,
+ * audioCodecsFilter:PCMA,PCMU,OPUS,
+ * videoCodecsFilter:VP8,H264,
+ * opusFmtpCodecsParameters:maxaveragebitrate=128000,
+ * }
+ *

+ * @returns {WebRTCommCall} new created WebRTCommCall object + * @throw {String} Exception "bad argument, check API documentation" + * @throw {String} Exception "bad configuration, missing parameter" + * @throw {String} Exception "bad state, unauthorized action" + */ +WebRTCommClient.prototype.call = function(calleePhoneNumber, callConfiguration) { + console.debug("WebRTCommClient:call():calleePhoneNumber=" + calleePhoneNumber); + console.debug("WebRTCommClient:call():callConfiguration=" + JSON.stringify(callConfiguration)); + try { + if (typeof(calleePhoneNumber) === 'string' && typeof(callConfiguration) === 'object') { + if (this.isOpened()) { + var newWebRTCommCall = new WebRTCommCall(this); + newWebRTCommCall.connector = this.connector.createPrivateSessionConnector(newWebRTCommCall); + newWebRTCommCall.open(calleePhoneNumber, callConfiguration); + return newWebRTCommCall; + } else { + console.error("WebRTCommClient:call(): bad state, unauthorized action"); + throw "WebRTCommClient:call(): bad state, unauthorized action"; + } + } else { + console.error("WebRTCommClient:call(): bad argument, check API documentation"); + throw "WebRTCommClient:call(): bad argument, check API documentation" + } + } catch (exception) { + console.error("WebRTCommClient:call(): catched exception:" + exception); + throw exception; + } +}; + + +/** + * Check validity of the client configuration + * @private + * @param {object} configuration client configuration + * *

Client configuration sample:
+ * {
+ * communicationMode:WebRTCommClient.prototype.SIP,
+ * sip: {,
+ * sipUriContactParameters:undefined,
+ * sipUserAgent:"WebRTCommTestWebApp/0.0.1",
+ * sipUserAgentCapabilities=undefined,
+ * sipOutboundProxy:"ws://localhost:5082",
+ * sipDomain:"sip.net",
+ * sipUserName:"alice",
+ * sipLogin:"alice@sip.net,
+ * sipPassword:"1234567890",
+ * sipRegisterMode:true,
+ * }
+ * RTCPeerConnection: {,
+ * + * }
+ * }
+ *

+ * @returns {boolean} true valid false unvalid + */ +WebRTCommClient.prototype.checkConfiguration = function(configuration) { + // don't want the password part of the configuration logged, so let's make a deep copy of 'configuration' and then delete the password key/value + var passwordSafeConfiguration = JSON.parse(JSON.stringify(configuration)); + if (configuration.sip.sipPassword != null) { + delete passwordSafeConfiguration.sip.sipPassword; + } + + console.debug("WebRTCommClient:checkConfiguration(): configuration=" + JSON.stringify(passwordSafeConfiguration)); + var check = true; + if (configuration.communicationMode !== undefined) { + if (configuration.communicationMode === WebRTCommClient.prototype.SIP) {} else { + check = false; + console.error("WebRTCommClient:checkConfiguration(): unsupported communicationMode"); + } + } else { + check = false; + console.error("WebRTCommClient:checkConfiguration(): missing configuration parameter communicationMode"); + } + return check; +}; + +/** + * Implements PrivateClientConnector opened event listener interface + * @private + */ +WebRTCommClient.prototype.onPrivateClientConnectorOpenedEvent = function() { + console.debug("WebRTCommClient:onPrivateClientConnectorOpenedEvent()"); + if (this.eventListener.onWebRTCommClientOpenedEvent !== undefined) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommClientOpenedEvent(); + } catch (exception) { + console.error("WebRTCommClient:onPrivateClientConnectorOpenedEvent(): catched exception in event listener:" + exception); + } + }, 1); + } +}; + +/** + * Implements PrivateClientConnector error event listener interface + * @private + * @param {string} error Error message + */ +WebRTCommClient.prototype.onPrivateClientConnectorOpenErrorEvent = function(error) { + console.debug("WebRTCommClient:onPrivateClientConnectorOpenErrorEvent():error:" + error); + // Force closing of the client + try { + this.close(); + } catch (exception) {} + + if (this.eventListener.onWebRTCommClientOpenErrorEvent !== undefined) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommClientOpenErrorEvent(error); + } catch (exception) { + console.error("WebRTCommClient:onPrivateClientConnectorOpenErrorEvent(): catched exception in event listener:" + exception); + } + }, 1); + } +}; + +/** + * Implements PrivateClientConnector closed event listener interface + * @callback PrivatePrivateClientConnector interface + * @private + */ + +WebRTCommClient.prototype.onPrivateClientConnectorClosedEvent = function() { + console.debug("WebRTCommClient:onPrivateClientConnectorClosedEvent()"); + var wasOpenedFlag = this.isOpened() || this.closePendingFlag; + + // Close properly the client + try { + if (this.closePendingFlag === false) + this.close(); + else + this.connector = undefined; + } catch (exception) {} + + if (wasOpenedFlag && (this.eventListener.onWebRTCommClientClosedEvent !== undefined)) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommClientClosedEvent(); + } catch (exception) { + console.error("WebRTCommClient:onPrivateClientConnectorClosedEvent(): catched exception in event listener:" + exception); + } + }, 1); + } else if (!wasOpenedFlag && (this.eventListener.onWebRTCommClientOpenErrorEvent !== undefined)) { + var that = this; + setTimeout(function() { + try { + that.eventListener.onWebRTCommClientOpenErrorEvent("Connection to WebRTCommServer has failed"); + } catch (exception) { + console.error("WebRTCommClient:onWebRTCommClientOpenErrorEvent(): catched exception in event listener:" + exception); + } + }, 1); + } +}; + +// Notice that in order to gain some speed (since this will be invoked A LOT), we use a hardcoded number of digits +// This add padding for a padding size that is equal or less than 2x inputNumber string length. For bigger padding sizes +// it will just return original inputNumber +function padNumberWithZeroes(inputNumber, paddingSize) { + // nothing to do if padding size is smaller or equal than string length, lets bail right away + if (paddingSize <= inputNumber.toString().length) { + return inputNumber; + } + var s = "000000000" + inputNumber; + + // With this method we can pad only if requested paddingSize if at most 2x number length. If more than that + // let's return original number to be safe + if (paddingSize > 2 * (inputNumber.toString().length)) { + return inputNumber; + } + + return s.substr(s.length - paddingSize); +} + +// Retrieve a timestamp string in the form: YYYY-MM-DD HH-MM-SS.MMMM +function getTimestamp() +{ + var currentDate = new Date(); + var timestamp = currentDate.getFullYear() + "-" + + padNumberWithZeroes(currentDate.getDate(), 2) + "-" + + padNumberWithZeroes((currentDate.getMonth() + 1), 2) + " " + + padNumberWithZeroes(currentDate.getHours(), 2) + ":" + + padNumberWithZeroes(currentDate.getMinutes(), 2) + ":" + + padNumberWithZeroes(currentDate.getSeconds(), 2) + "." + + padNumberWithZeroes(currentDate.getMilliseconds(), 3); + return timestamp; +} + +// Common logging function called by all the others with appropriate logging function +function commonLog(logger, args, includeStackTrace) +{ + var e = new Error('dummy-exception'); + var stack = e.stack; + var isFirefox = typeof InstallTrigger !== 'undefined'; + + if (!isFirefox) { + // specially for chrome there's a header in the stack that we need to remove + stack = stack.replace(/^.*?dummy-exception.*?\n/gm, ''); + } + + if (includeStackTrace !== undefined && includeStackTrace == true) { + if (isFirefox) { + // stack trace has been requested, let's add tabs in the beginning for beautification (just for Firefox since for Chrome its already beautified) + stack = stack.replace(/^/gm, "\t"); + } + } + else { + stack = stack.split('\n'); + } + + // what this does is prepend element given as second argument, in array given as first argument + Array.prototype.unshift.call(args, getTimestamp()); + + if (includeStackTrace !== undefined && includeStackTrace == true) { + // similarly, this appends the seconds argument, hence the stack trace, to the args array + Array.prototype.push.call(args, "\n\nStack trace: \n" + stack); + } + else { + var checkedStack; + // normally stack should have at least 3 elements: current function, startup setup function below, and actual calling point, + // but let's add a check just in case + if (stack.length >= 3) { + checkedStack = stack[2]; + checkedStack = checkedStack.replace(/^\s*at\s*/, '') + } + else { + checkedStack = stack; + } + Array.prototype.push.call(args, "\n\t[" + checkedStack + "] "); + } + + // do the actual logging + logger.apply(this, args); +} + +// Let's override the console logging methods, to be able to add timestamps always +(function() { + if (window.console && console.debug) { + var oldConsoleDebug = console.debug; + console.debug = function() { + commonLog(oldConsoleDebug, arguments); + } + } + if (window.console && console.log) { + var oldConsoleLog = console.log; + console.log = function() { + commonLog(oldConsoleLog, arguments); + } + } + if (window.console && console.info) { + var oldConsoleInfo = console.info; + console.info = function() { + commonLog(oldConsoleInfo, arguments); + } + } + if (window.console && console.warn) { + var oldConsoleWarn = console.warn; + console.warn = function() { + commonLog(oldConsoleWarn, arguments); + } + } + if (window.console && console.error) { + var oldConsoleError = console.error; + console.error = function() { + commonLog(oldConsoleError, arguments, true); + } + } +})(); + +/** + * @class WebRTCommClientEventListenerInterface + * @classdesc Abstract class describing WebRTCommClient event listener interface + * required to be implented by the webapp + * @constructor + * @public + */ +WebRTCommClientEventListenerInterface = function() {}; + +/** + * Open event + * @public + */ +WebRTCommClientEventListenerInterface.prototype.onWebRTCommClientOpenedEvent = function() { + throw "WebRTCommClientEventListenerInterface:onWebRTCommClientOpenedEvent(): not implemented;"; +}; + +/** + * Open error event + * @public + * @param {String} error open error message + */ +WebRTCommClientEventListenerInterface.prototype.onWebRTCommClientOpenErrorEvent = function(error) { + throw "WebRTCommClientEventListenerInterface:onWebRTCommClientOpenErrorEvent(): not implemented;"; +}; + + +/** + * Close event + * @public + */ +WebRTCommClientEventListenerInterface.prototype.onWebRTCommClientClosedEvent = function() { + throw "WebRTCommClientEventListenerInterface:onWebRTCommClientClosedEvent(): not implemented;"; +};/** + * @class WebRTCommCallEventListenerInterface + * @classdesc Abstract class describing WebRTCommClient event listener interface + * required to be implented by the webapp + * @constructor + * @public + */ +WebRTCommCallEventListenerInterface = function() {}; + +/** + * Open event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallOpenedEvent = function(webRTCommCall) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallOpenedEvent(): not implemented;"; +}; + + +/** + * In progress event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallInProgressEvent = function(webRTCommCall) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallInProgressEvent(): not implemented;"; +}; + +/** + * Open error event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + * @param {String} error error message + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallOpenErrorEvent = function(webRTCommCall, error) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallOpenErrorEvent(): not implemented;"; +}; + +/** + * Call error event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + * @param {String} error error message + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallErrorEvent = function(webRTCommCall, error) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallErrorEvent(): not implemented;"; +}; + +/** + * Ringing event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallRingingEvent = function(webRTCommCall) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallRingingEvent(): not implemented;"; +}; + +/** + * Ringback event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallRingingBackEvent = function(webRTCommCall) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallRingingBackEvent(): not implemented;"; +}; + +/** + * Hangup event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallHangupEvent = function(webRTCommCall) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallHangupEvent(): not implemented;"; +}; + +/** + * Webrtc stats event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + * @param {stats} stats for the Webrtc call + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallStatsEvent = function(webRTCommCall, stats) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallStatsEvent(): not implemented;"; +}; + +/** + * Incoming call Cancel event + * @public + * @param {WebRTCommCall} webRTCommCall source WebRTCommCall object + */ +WebRTCommCallEventListenerInterface.prototype.onWebRTCommCallCanceledEvent = function(webRTCommCall) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommCallCanceledEvent(): not implemented;"; +};/** + * @class WebRTCommMessageEventListenerInterface + * @classdesc Abstract class describing WebRTCommMessage event listener interface + * required to be implented by the webapp + * @constructor + * @public + */ +WebRTCommMessageEventListenerInterface = function() {}; + + +/** + * Received message event + * @public + * @param {WebRTCommMessage} message object + */ +WebRTCommMessageEventListenerInterface.prototype.onWebRTCommMessageReceivedEvent = function(message) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommMessageReceivedEvent(): not implemented;"; +}; + +/** + * Received message event + * @public + * @param {WebRTCommMessage} message object + */ +WebRTCommMessageEventListenerInterface.prototype.onWebRTCommMessageSentEvent = function(message) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommMessageSentEvent(): not implemented;"; +}; + +/** + * Send message error event + * @public + * @param {WebRTCommMessage} message object + * @param {String} error code + */ +WebRTCommMessageEventListenerInterface.prototype.onWebRTCommMessageSendErrorEvent = function(message, error) { + throw "WebRTCommCallEventListenerInterface:onWebRTCommMessageSendErrorEvent(): not implemented;"; +}; \ No newline at end of file diff --git a/samples/screen-sharing/js/lib/adapter.js b/samples/screen-sharing/js/lib/adapter.js new file mode 100644 index 0000000..fd17d04 --- /dev/null +++ b/samples/screen-sharing/js/lib/adapter.js @@ -0,0 +1,1574 @@ +// Copyright (c) 2014, The WebRTC project authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * 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. +// +// * Neither the name of Google 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 +// HOLDER 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. + +/* More information about these options at jshint.com/docs/options */ +/* jshint browser: true, camelcase: true, curly: true, devel: true, + eqeqeq: true, forin: false, globalstrict: true, node: true, + quotmark: single, undef: true, unused: strict */ +/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise, +mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */ +/* exported requestUserMedia */ + +'use strict'; + +var getUserMedia = null; +var attachMediaStream = null; +var reattachMediaStream = null; +var webrtcDetectedBrowser = null; +var webrtcDetectedVersion = null; +var webrtcMinimumVersion = null; +var webrtcUtils = { + log: function() { + // suppress console.log output when being included as a module. + if (typeof module !== 'undefined' || + typeof require === 'function' && typeof define === 'function') { + return; + } + console.log.apply(console, arguments); + } +}; + +if (typeof window === 'object') { + if (window.HTMLMediaElement && + !('srcObject' in window.HTMLMediaElement.prototype)) { + // Shim the srcObject property, once, when HTMLMediaElement is found. + Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', { + get: function() { + // If prefixed srcObject property exists, return it. + // Otherwise use the shimmed property, _srcObject + return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject; + }, + set: function(stream) { + if ('mozSrcObject' in this) { + this.mozSrcObject = stream; + } else { + // Use _srcObject as a private property for this shim + this._srcObject = stream; + // TODO: revokeObjectUrl(this.src) when !stream to release resources? + this.src = URL.createObjectURL(stream); + } + } + }); + } + // Proxy existing globals + getUserMedia = window.navigator && window.navigator.getUserMedia; +} + +// Attach a media stream to an element. +attachMediaStream = function(element, stream) { + element.srcObject = stream; +}; + +reattachMediaStream = function(to, from) { + to.srcObject = from.srcObject; +}; + +if (typeof window === 'undefined' || !window.navigator) { + webrtcUtils.log('This does not appear to be a browser'); + webrtcDetectedBrowser = 'not a browser'; +} else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) { + webrtcUtils.log('This appears to be Firefox'); + + webrtcDetectedBrowser = 'firefox'; + + // the detected firefox version. + webrtcDetectedVersion = + parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); + + // the minimum firefox version still supported by adapter. + webrtcMinimumVersion = 31; + + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + if (webrtcDetectedVersion < 38) { + // .urls is not supported in FF < 38. + // create RTCIceServers with a single url. + if (pcConfig && pcConfig.iceServers) { + var newIceServers = []; + for (var i = 0; i < pcConfig.iceServers.length; i++) { + var server = pcConfig.iceServers[i]; + if (server.hasOwnProperty('urls')) { + for (var j = 0; j < server.urls.length; j++) { + var newServer = { + url: server.urls[j] + }; + if (server.urls[j].indexOf('turn') === 0) { + newServer.username = server.username; + newServer.credential = server.credential; + } + newIceServers.push(newServer); + } + } else { + newIceServers.push(pcConfig.iceServers[i]); + } + } + pcConfig.iceServers = newIceServers; + } + } + return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + }; + + // The RTCSessionDescription object. + if (!window.RTCSessionDescription) { + window.RTCSessionDescription = mozRTCSessionDescription; + } + + // The RTCIceCandidate object. + if (!window.RTCIceCandidate) { + window.RTCIceCandidate = mozRTCIceCandidate; + } + + // getUserMedia constraints shim. + getUserMedia = function(constraints, onSuccess, onError) { + var constraintsToFF37 = function(c) { + if (typeof c !== 'object' || c.require) { + return c; + } + var require = []; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = c[key] = (typeof c[key] === 'object') ? + c[key] : {ideal: c[key]}; + if (r.min !== undefined || + r.max !== undefined || r.exact !== undefined) { + require.push(key); + } + if (r.exact !== undefined) { + if (typeof r.exact === 'number') { + r.min = r.max = r.exact; + } else { + c[key] = r.exact; + } + delete r.exact; + } + if (r.ideal !== undefined) { + c.advanced = c.advanced || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[key] = {min: r.ideal, max: r.ideal}; + } else { + oc[key] = r.ideal; + } + c.advanced.push(oc); + delete r.ideal; + if (!Object.keys(r).length) { + delete c[key]; + } + } + }); + if (require.length) { + c.require = require; + } + return c; + }; + if (webrtcDetectedVersion < 38) { + webrtcUtils.log('spec: ' + JSON.stringify(constraints)); + if (constraints.audio) { + constraints.audio = constraintsToFF37(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToFF37(constraints.video); + } + webrtcUtils.log('ff37: ' + JSON.stringify(constraints)); + } + return navigator.mozGetUserMedia(constraints, onSuccess, onError); + }; + + navigator.getUserMedia = getUserMedia; + + // Shim for mediaDevices on older versions. + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + addEventListener: function() { }, + removeEventListener: function() { } + }; + } + navigator.mediaDevices.enumerateDevices = + navigator.mediaDevices.enumerateDevices || function() { + return new Promise(function(resolve) { + var infos = [ + {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''}, + {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''} + ]; + resolve(infos); + }); + }; + + if (webrtcDetectedVersion < 41) { + // Work around http://bugzil.la/1169665 + var orgEnumerateDevices = + navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices); + navigator.mediaDevices.enumerateDevices = function() { + return orgEnumerateDevices().catch(function(e) { + if (e.name === 'NotFoundError') { + return []; + } + throw e; + }); + }; + } +} else if (navigator.webkitGetUserMedia && !!window.chrome) { + webrtcUtils.log('This appears to be Chrome'); + + webrtcDetectedBrowser = 'chrome'; + + // the detected chrome version. + webrtcDetectedVersion = + parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10); + + // the minimum chrome version still supported by adapter. + webrtcMinimumVersion = 38; + + // The RTCPeerConnection object. + window.RTCPeerConnection = function(pcConfig, pcConstraints) { + // Translate iceTransportPolicy to iceTransports, + // see https://code.google.com/p/webrtc/issues/detail?id=4869 + if (pcConfig && pcConfig.iceTransportPolicy) { + pcConfig.iceTransports = pcConfig.iceTransportPolicy; + } + + var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors + var origGetStats = pc.getStats.bind(pc); + pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line + var self = this; + var args = arguments; + + // If selector is a function then we are in the old style stats so just + // pass back the original getStats format to avoid breaking old users. + if (arguments.length > 0 && typeof selector === 'function') { + return origGetStats(selector, successCallback); + } + + var fixChromeStats = function(response) { + var standardReport = {}; + var reports = response.result(); + reports.forEach(function(report) { + var standardStats = { + id: report.id, + timestamp: report.timestamp, + type: report.type + }; + report.names().forEach(function(name) { + standardStats[name] = report.stat(name); + }); + standardReport[standardStats.id] = standardStats; + }); + + return standardReport; + }; + + if (arguments.length >= 2) { + var successCallbackWrapper = function(response) { + args[1](fixChromeStats(response)); + }; + + return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]); + } + + // promise-support + return new Promise(function(resolve, reject) { + if (args.length === 1 && selector === null) { + origGetStats.apply(self, [ + function(response) { + resolve.apply(null, [fixChromeStats(response)]); + }, reject]); + } else { + origGetStats.apply(self, [resolve, reject]); + } + }); + }; + + return pc; + }; + + // add promise support + ['createOffer', 'createAnswer'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var self = this; + if (arguments.length < 1 || (arguments.length === 1 && + typeof(arguments[0]) === 'object')) { + var opts = arguments.length === 1 ? arguments[0] : undefined; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [resolve, reject, opts]); + }); + } else { + return nativeMethod.apply(this, arguments); + } + }; + }); + + ['setLocalDescription', 'setRemoteDescription', + 'addIceCandidate'].forEach(function(method) { + var nativeMethod = webkitRTCPeerConnection.prototype[method]; + webkitRTCPeerConnection.prototype[method] = function() { + var args = arguments; + var self = this; + return new Promise(function(resolve, reject) { + nativeMethod.apply(self, [args[0], + function() { + resolve(); + if (args.length >= 2) { + args[1].apply(null, []); + } + }, + function(err) { + reject(err); + if (args.length >= 3) { + args[2].apply(null, [err]); + } + }] + ); + }); + }; + }); + + // getUserMedia constraints shim. + var constraintsToChrome = function(c) { + if (typeof c !== 'object' || c.mandatory || c.optional) { + return c; + } + var cc = {}; + Object.keys(c).forEach(function(key) { + if (key === 'require' || key === 'advanced' || key === 'mediaSource') { + return; + } + var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; + if (r.exact !== undefined && typeof r.exact === 'number') { + r.min = r.max = r.exact; + } + var oldname = function(prefix, name) { + if (prefix) { + return prefix + name.charAt(0).toUpperCase() + name.slice(1); + } + return (name === 'deviceId') ? 'sourceId' : name; + }; + if (r.ideal !== undefined) { + cc.optional = cc.optional || []; + var oc = {}; + if (typeof r.ideal === 'number') { + oc[oldname('min', key)] = r.ideal; + cc.optional.push(oc); + oc = {}; + oc[oldname('max', key)] = r.ideal; + cc.optional.push(oc); + } else { + oc[oldname('', key)] = r.ideal; + cc.optional.push(oc); + } + } + if (r.exact !== undefined && typeof r.exact !== 'number') { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname('', key)] = r.exact; + } else { + ['min', 'max'].forEach(function(mix) { + if (r[mix] !== undefined) { + cc.mandatory = cc.mandatory || {}; + cc.mandatory[oldname(mix, key)] = r[mix]; + } + }); + } + }); + if (c.advanced) { + cc.optional = (cc.optional || []).concat(c.advanced); + } + return cc; + }; + + getUserMedia = function(constraints, onSuccess, onError) { + if (constraints.audio) { + constraints.audio = constraintsToChrome(constraints.audio); + } + if (constraints.video) { + constraints.video = constraintsToChrome(constraints.video); + } + webrtcUtils.log('chrome: ' + JSON.stringify(constraints)); + return navigator.webkitGetUserMedia(constraints, onSuccess, onError); + }; + navigator.getUserMedia = getUserMedia; + + if (!navigator.mediaDevices) { + navigator.mediaDevices = {getUserMedia: requestUserMedia, + enumerateDevices: function() { + return new Promise(function(resolve) { + var kinds = {audio: 'audioinput', video: 'videoinput'}; + return MediaStreamTrack.getSources(function(devices) { + resolve(devices.map(function(device) { + return {label: device.label, + kind: kinds[device.kind], + deviceId: device.id, + groupId: ''}; + })); + }); + }); + }}; + } + + // A shim for getUserMedia method on the mediaDevices object. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (!navigator.mediaDevices.getUserMedia) { + navigator.mediaDevices.getUserMedia = function(constraints) { + return requestUserMedia(constraints); + }; + } else { + // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia + // function which returns a Promise, it does not accept spec-style + // constraints. + var origGetUserMedia = navigator.mediaDevices.getUserMedia. + bind(navigator.mediaDevices); + navigator.mediaDevices.getUserMedia = function(c) { + webrtcUtils.log('spec: ' + JSON.stringify(c)); // whitespace for alignment + c.audio = constraintsToChrome(c.audio); + c.video = constraintsToChrome(c.video); + webrtcUtils.log('chrome: ' + JSON.stringify(c)); + return origGetUserMedia(c); + }; + } + + // Dummy devicechange event methods. + // TODO(KaptenJansson) remove once implemented in Chrome stable. + if (typeof navigator.mediaDevices.addEventListener === 'undefined') { + navigator.mediaDevices.addEventListener = function() { + webrtcUtils.log('Dummy mediaDevices.addEventListener called.'); + }; + } + if (typeof navigator.mediaDevices.removeEventListener === 'undefined') { + navigator.mediaDevices.removeEventListener = function() { + webrtcUtils.log('Dummy mediaDevices.removeEventListener called.'); + }; + } + + // Attach a media stream to an element. + attachMediaStream = function(element, stream) { + if (webrtcDetectedVersion >= 43) { + element.srcObject = stream; + } else if (typeof element.src !== 'undefined') { + element.src = URL.createObjectURL(stream); + } else { + webrtcUtils.log('Error attaching stream to element.'); + } + }; + reattachMediaStream = function(to, from) { + if (webrtcDetectedVersion >= 43) { + to.srcObject = from.srcObject; + } else { + to.src = from.src; + } + }; + +} else if (navigator.mediaDevices && navigator.userAgent.match( + /Edge\/(\d+).(\d+)$/)) { + webrtcUtils.log('This appears to be Edge'); + webrtcDetectedBrowser = 'edge'; + + webrtcDetectedVersion = + parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10); + + // the minimum version still supported by adapter. + webrtcMinimumVersion = 12; + + if (RTCIceGatherer) { + window.RTCIceCandidate = function(args) { + return args; + }; + window.RTCSessionDescription = function(args) { + return args; + }; + + window.RTCPeerConnection = function(config) { + var self = this; + + this.onicecandidate = null; + this.onaddstream = null; + this.onremovestream = null; + this.onsignalingstatechange = null; + this.oniceconnectionstatechange = null; + this.onnegotiationneeded = null; + this.ondatachannel = null; + + this.localStreams = []; + this.remoteStreams = []; + this.getLocalStreams = function() { return self.localStreams; }; + this.getRemoteStreams = function() { return self.remoteStreams; }; + + this.localDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.remoteDescription = new RTCSessionDescription({ + type: '', + sdp: '' + }); + this.signalingState = 'stable'; + this.iceConnectionState = 'new'; + + this.iceOptions = { + gatherPolicy: 'all', + iceServers: [] + }; + if (config && config.iceTransportPolicy) { + switch (config.iceTransportPolicy) { + case 'all': + case 'relay': + this.iceOptions.gatherPolicy = config.iceTransportPolicy; + break; + case 'none': + // FIXME: remove once implementation and spec have added this. + throw new TypeError('iceTransportPolicy "none" not supported'); + } + } + if (config && config.iceServers) { + this.iceOptions.iceServers = config.iceServers; + } + + // per-track iceGathers etc + this.mLines = []; + + this._iceCandidates = []; + + this._peerConnectionId = 'PC_' + Math.floor(Math.random() * 65536); + + // FIXME: Should be generated according to spec (guid?) + // and be the same for all PCs from the same JS + this._cname = Math.random().toString(36).substr(2, 10); + }; + + window.RTCPeerConnection.prototype.addStream = function(stream) { + // clone just in case we're working in a local demo + // FIXME: seems to be fixed + this.localStreams.push(stream.clone()); + + // FIXME: maybe trigger negotiationneeded? + }; + + window.RTCPeerConnection.prototype.removeStream = function(stream) { + var idx = this.localStreams.indexOf(stream); + if (idx > -1) { + this.localStreams.splice(idx, 1); + } + // FIXME: maybe trigger negotiationneeded? + }; + + // SDP helper from sdp-jingle-json with modifications. + window.RTCPeerConnection.prototype._toCandidateJSON = function(line) { + var parts; + if (line.indexOf('a=candidate:') === 0) { + parts = line.substring(12).split(' '); + } else { // no a=candidate + parts = line.substring(10).split(' '); + } + + var candidate = { + foundation: parts[0], + component: parts[1], + protocol: parts[2].toLowerCase(), + priority: parseInt(parts[3], 10), + ip: parts[4], + port: parseInt(parts[5], 10), + // skip parts[6] == 'typ' + type: parts[7] + //generation: '0' + }; + + for (var i = 8; i < parts.length; i += 2) { + if (parts[i] === 'raddr') { + candidate.relatedAddress = parts[i + 1]; // was: relAddr + } else if (parts[i] === 'rport') { + candidate.relatedPort = parseInt(parts[i + 1], 10); // was: relPort + } else if (parts[i] === 'generation') { + candidate.generation = parts[i + 1]; + } else if (parts[i] === 'tcptype') { + candidate.tcpType = parts[i + 1]; + } + } + return candidate; + }; + + // SDP helper from sdp-jingle-json with modifications. + window.RTCPeerConnection.prototype._toCandidateSDP = function(candidate) { + var sdp = []; + sdp.push(candidate.foundation); + sdp.push(candidate.component); + sdp.push(candidate.protocol.toUpperCase()); + sdp.push(candidate.priority); + sdp.push(candidate.ip); + sdp.push(candidate.port); + + var type = candidate.type; + sdp.push('typ'); + sdp.push(type); + if (type === 'srflx' || type === 'prflx' || type === 'relay') { + if (candidate.relatedAddress && candidate.relatedPort) { + sdp.push('raddr'); + sdp.push(candidate.relatedAddress); // was: relAddr + sdp.push('rport'); + sdp.push(candidate.relatedPort); // was: relPort + } + } + if (candidate.tcpType && candidate.protocol.toUpperCase() === 'TCP') { + sdp.push('tcptype'); + sdp.push(candidate.tcpType); + } + return 'a=candidate:' + sdp.join(' '); + }; + + // SDP helper from sdp-jingle-json with modifications. + window.RTCPeerConnection.prototype._parseRtpMap = function(line) { + var parts = line.substr(9).split(' '); + var parsed = { + payloadType: parseInt(parts.shift(), 10) // was: id + }; + + parts = parts[0].split('/'); + + parsed.name = parts[0]; + parsed.clockRate = parseInt(parts[1], 10); // was: clockrate + parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels + return parsed; + }; + + // Parses SDP to determine capabilities. + window.RTCPeerConnection.prototype._getRemoteCapabilities = + function(section) { + var remoteCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + var i; + var lines = section.split('\r\n'); + var mline = lines[0].substr(2).split(' '); + var rtpmapFilter = function(line) { + return line.indexOf('a=rtpmap:' + mline[i]) === 0; + }; + var fmtpFilter = function(line) { + return line.indexOf('a=fmtp:' + mline[i]) === 0; + }; + var parseFmtp = function(line) { + var parsed = {}; + var kv; + var parts = line.substr(('a=fmtp:' + mline[i]).length + 1).split(';'); + for (var j = 0; j < parts.length; j++) { + kv = parts[j].split('='); + parsed[kv[0].trim()] = kv[1]; + } + console.log('fmtp', mline[i], parsed); + return parsed; + }; + var rtcpFbFilter = function(line) { + return line.indexOf('a=rtcp-fb:' + mline[i]) === 0; + }; + var parseRtcpFb = function(line) { + var parts = line.substr(('a=rtcp-fb:' + mline[i]).length + 1) + .split(' '); + return { + type: parts.shift(), + parameter: parts.join(' ') + }; + }; + for (i = 3; i < mline.length; i++) { // find all codecs from mline[3..] + var line = lines.filter(rtpmapFilter)[0]; + if (line) { + var codec = this._parseRtpMap(line); + + var fmtp = lines.filter(fmtpFilter); + codec.parameters = fmtp.length ? parseFmtp(fmtp[0]) : {}; + codec.rtcpFeedback = lines.filter(rtcpFbFilter).map(parseRtcpFb); + + remoteCapabilities.codecs.push(codec); + } + } + return remoteCapabilities; + }; + + // Serializes capabilities to SDP. + window.RTCPeerConnection.prototype._capabilitiesToSDP = function(caps) { + var sdp = ''; + caps.codecs.forEach(function(codec) { + var pt = codec.payloadType; + if (codec.preferredPayloadType !== undefined) { + pt = codec.preferredPayloadType; + } + sdp += 'a=rtpmap:' + pt + + ' ' + codec.name + + '/' + codec.clockRate + + (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + + '\r\n'; + if (codec.parameters && codec.parameters.length) { + sdp += 'a=ftmp:' + pt + ' '; + Object.keys(codec.parameters).forEach(function(param) { + sdp += param + '=' + codec.parameters[param]; + }); + sdp += '\r\n'; + } + if (codec.rtcpFeedback) { + // FIXME: special handling for trr-int? + codec.rtcpFeedback.forEach(function(fb) { + sdp += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' + + fb.parameter + '\r\n'; + }); + } + }); + return sdp; + }; + + // Calculates the intersection of local and remote capabilities. + window.RTCPeerConnection.prototype._getCommonCapabilities = + function(localCapabilities, remoteCapabilities) { + var commonCapabilities = { + codecs: [], + headerExtensions: [], + fecMechanisms: [] + }; + localCapabilities.codecs.forEach(function(lCodec) { + for (var i = 0; i < remoteCapabilities.codecs.length; i++) { + var rCodec = remoteCapabilities.codecs[i]; + if (lCodec.name === rCodec.name && + lCodec.clockRate === rCodec.clockRate && + lCodec.numChannels === rCodec.numChannels) { + // push rCodec so we reply with offerer payload type + commonCapabilities.codecs.push(rCodec); + + // FIXME: also need to calculate intersection between + // .rtcpFeedback and .parameters + break; + } + } + }); + + localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { + for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) { + var rHeaderExtension = remoteCapabilities.headerExtensions[i]; + if (lHeaderExtension.uri === rHeaderExtension.uri) { + commonCapabilities.headerExtensions.push(rHeaderExtension); + break; + } + } + }); + + // FIXME: fecMechanisms + return commonCapabilities; + }; + + // Parses DTLS parameters from SDP section or sessionpart. + window.RTCPeerConnection.prototype._getDtlsParameters = + function(section, session) { + var lines = section.split('\r\n'); + lines = lines.concat(session.split('\r\n')); // Search in session part, too. + var fpLine = lines.filter(function(line) { + return line.indexOf('a=fingerprint:') === 0; + }); + fpLine = fpLine[0].substr(14); + var dtlsParameters = { + role: 'auto', + fingerprints: [{ + algorithm: fpLine.split(' ')[0], + value: fpLine.split(' ')[1] + }] + }; + return dtlsParameters; + }; + + // Serializes DTLS parameters to SDP. + window.RTCPeerConnection.prototype._dtlsParametersToSDP = + function(params, setupType) { + var sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(function(fp) { + sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; + }); + return sdp; + }; + + // Parses ICE information from SDP section or sessionpart. + window.RTCPeerConnection.prototype._getIceParameters = + function(section, session) { + var lines = section.split('\r\n'); + lines = lines.concat(session.split('\r\n')); // Search in session part, too. + var iceParameters = { + usernameFragment: lines.filter(function(line) { + return line.indexOf('a=ice-ufrag:') === 0; + })[0].substr(12), + password: lines.filter(function(line) { + return line.indexOf('a=ice-pwd:') === 0; + })[0].substr(10), + }; + return iceParameters; + }; + + // Serializes ICE parameters to SDP. + window.RTCPeerConnection.prototype._iceParametersToSDP = function(params) { + return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + + 'a=ice-pwd:' + params.password + '\r\n'; + }; + + window.RTCPeerConnection.prototype._getEncodingParameters = function(ssrc) { + return { + ssrc: ssrc, + codecPayloadType: 0, + fec: 0, + rtx: 0, + priority: 1.0, + maxBitrate: 2000000.0, + minQuality: 0, + framerateBias: 0.5, + resolutionScale: 1.0, + framerateScale: 1.0, + active: true, + dependencyEncodingId: undefined, + encodingId: undefined + }; + }; + + // Create ICE gatherer, ICE transport and DTLS transport. + window.RTCPeerConnection.prototype._createIceAndDtlsTransports = + function(mid, sdpMLineIndex) { + var self = this; + var iceGatherer = new RTCIceGatherer(self.iceOptions); + var iceTransport = new RTCIceTransport(iceGatherer); + iceGatherer.onlocalcandidate = function(evt) { + var event = {}; + event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; + + var cand = evt.candidate; + var isEndOfCandidates = !(cand && Object.keys(cand).length > 0); + if (isEndOfCandidates) { + event.candidate.candidate = + 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates'; + } else { + // RTCIceCandidate doesn't have a component, needs to be added + cand.component = iceTransport.component === 'RTCP' ? 2 : 1; + event.candidate.candidate = self._toCandidateSDP(cand); + } + if (self.onicecandidate !== null) { + if (self.localDescription && self.localDescription.type === '') { + self._iceCandidates.push(event); + } else { + self.onicecandidate(event); + } + } + }; + iceTransport.onicestatechange = function() { + /* + console.log(self._peerConnectionId, + 'ICE state change', iceTransport.state); + */ + self._updateIceConnectionState(iceTransport.state); + }; + + var dtlsTransport = new RTCDtlsTransport(iceTransport); + dtlsTransport.ondtlsstatechange = function() { + /* + console.log(self._peerConnectionId, sdpMLineIndex, + 'dtls state change', dtlsTransport.state); + */ + }; + dtlsTransport.onerror = function(error) { + console.error('dtls error', error); + }; + return { + iceGatherer: iceGatherer, + iceTransport: iceTransport, + dtlsTransport: dtlsTransport + }; + }; + + window.RTCPeerConnection.prototype.setLocalDescription = + function(description) { + var self = this; + if (description.type === 'offer') { + if (!description.ortc) { + // FIXME: throw? + } else { + this.mLines = description.ortc; + } + } else if (description.type === 'answer') { + var sections = self.remoteDescription.sdp.split('\r\nm='); + var sessionpart = sections.shift(); + sections.forEach(function(section, sdpMLineIndex) { + section = 'm=' + section; + + var iceGatherer = self.mLines[sdpMLineIndex].iceGatherer; + var iceTransport = self.mLines[sdpMLineIndex].iceTransport; + var dtlsTransport = self.mLines[sdpMLineIndex].dtlsTransport; + var rtpSender = self.mLines[sdpMLineIndex].rtpSender; + var localCapabilities = + self.mLines[sdpMLineIndex].localCapabilities; + var remoteCapabilities = + self.mLines[sdpMLineIndex].remoteCapabilities; + var sendSSRC = self.mLines[sdpMLineIndex].sendSSRC; + var recvSSRC = self.mLines[sdpMLineIndex].recvSSRC; + + var remoteIceParameters = self._getIceParameters(section, + sessionpart); + iceTransport.start(iceGatherer, remoteIceParameters, 'controlled'); + + var remoteDtlsParameters = self._getDtlsParameters(section, + sessionpart); + dtlsTransport.start(remoteDtlsParameters); + + if (rtpSender) { + // calculate intersection of capabilities + var params = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + params.muxId = sendSSRC; + params.encodings = [self._getEncodingParameters(sendSSRC)]; + params.rtcp = { + cname: self._cname, + reducedSize: false, + ssrc: recvSSRC, + mux: true + }; + rtpSender.send(params); + } + }); + } + + this.localDescription = description; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-local-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + } + + // FIXME: need to _reliably_ execute after args[1] or promise + window.setTimeout(function() { + // FIXME: need to apply ice candidates in a way which is async but in-order + self._iceCandidates.forEach(function(event) { + if (self.onicecandidate !== null) { + self.onicecandidate(event); + } + }); + self._iceCandidates = []; + }, 50); + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return new Promise(function(resolve) { + resolve(); + }); + }; + + window.RTCPeerConnection.prototype.setRemoteDescription = + function(description) { + // FIXME: for type=offer this creates state. which should not + // happen before SLD with type=answer but... we need the stream + // here for onaddstream. + var self = this; + var sections = description.sdp.split('\r\nm='); + var sessionpart = sections.shift(); + var stream = new MediaStream(); + sections.forEach(function(section, sdpMLineIndex) { + section = 'm=' + section; + var lines = section.split('\r\n'); + var mline = lines[0].substr(2).split(' '); + var kind = mline[0]; + var line; + + var iceGatherer; + var iceTransport; + var dtlsTransport; + var rtpSender; + var rtpReceiver; + var sendSSRC; + var recvSSRC; + + var mid = lines.filter(function(line) { + return line.indexOf('a=mid:') === 0; + })[0].substr(6); + + var cname; + + var remoteCapabilities; + var params; + + if (description.type === 'offer') { + var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + var localCapabilities = RTCRtpReceiver.getCapabilities(kind); + // determine remote caps from SDP + remoteCapabilities = self._getRemoteCapabilities(section); + + line = lines.filter(function(line) { + return line.indexOf('a=ssrc:') === 0 && + line.split(' ')[1].indexOf('cname:') === 0; + }); + sendSSRC = (2 * sdpMLineIndex + 2) * 1001; + if (line) { // FIXME: alot of assumptions here + recvSSRC = line[0].split(' ')[0].split(':')[1]; + cname = line[0].split(' ')[1].split(':')[1]; + } + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + + // calculate intersection so no unknown caps get passed into the RTPReciver + params = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + + params.muxId = recvSSRC; + params.encodings = [self._getEncodingParameters(recvSSRC)]; + params.rtcp = { + cname: cname, + reducedSize: false, + ssrc: sendSSRC, + mux: true + }; + rtpReceiver.receive(params); + // FIXME: not correct when there are multiple streams but that is + // not currently supported. + stream.addTrack(rtpReceiver.track); + + // FIXME: honor a=sendrecv + if (self.localStreams.length > 0 && + self.localStreams[0].getTracks().length >= sdpMLineIndex) { + // FIXME: actually more complicated, needs to match types etc + var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex]; + rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport); + } + + self.mLines[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: remoteCapabilities, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + sendSSRC: sendSSRC, + recvSSRC: recvSSRC + }; + } else { + iceGatherer = self.mLines[sdpMLineIndex].iceGatherer; + iceTransport = self.mLines[sdpMLineIndex].iceTransport; + dtlsTransport = self.mLines[sdpMLineIndex].dtlsTransport; + rtpSender = self.mLines[sdpMLineIndex].rtpSender; + rtpReceiver = self.mLines[sdpMLineIndex].rtpReceiver; + sendSSRC = self.mLines[sdpMLineIndex].sendSSRC; + recvSSRC = self.mLines[sdpMLineIndex].recvSSRC; + } + + var remoteIceParameters = self._getIceParameters(section, sessionpart); + var remoteDtlsParameters = self._getDtlsParameters(section, + sessionpart); + + // for answers we start ice and dtls here, otherwise this is done in SLD + if (description.type === 'answer') { + iceTransport.start(iceGatherer, remoteIceParameters, 'controlling'); + dtlsTransport.start(remoteDtlsParameters); + + // determine remote caps from SDP + remoteCapabilities = self._getRemoteCapabilities(section); + // FIXME: store remote caps? + + if (rtpSender) { + params = remoteCapabilities; + params.muxId = sendSSRC; + params.encodings = [self._getEncodingParameters(sendSSRC)]; + params.rtcp = { + cname: self._cname, + reducedSize: false, + ssrc: recvSSRC, + mux: true + }; + rtpSender.send(params); + } + + // FIXME: only if a=sendrecv + var bidi = lines.filter(function(line) { + return line.indexOf('a=ssrc:') === 0; + }).length > 0; + if (rtpReceiver && bidi) { + line = lines.filter(function(line) { + return line.indexOf('a=ssrc:') === 0 && + line.split(' ')[1].indexOf('cname:') === 0; + }); + if (line) { // FIXME: alot of assumptions here + recvSSRC = line[0].split(' ')[0].split(':')[1]; + cname = line[0].split(' ')[1].split(':')[1]; + } + params = remoteCapabilities; + params.muxId = recvSSRC; + params.encodings = [self._getEncodingParameters(recvSSRC)]; + params.rtcp = { + cname: cname, + reducedSize: false, + ssrc: sendSSRC, + mux: true + }; + rtpReceiver.receive(params, kind); + stream.addTrack(rtpReceiver.track); + self.mLines[sdpMLineIndex].recvSSRC = recvSSRC; + } + } + }); + + this.remoteDescription = description; + switch (description.type) { + case 'offer': + this._updateSignalingState('have-remote-offer'); + break; + case 'answer': + this._updateSignalingState('stable'); + break; + } + window.setTimeout(function() { + if (self.onaddstream !== null && stream.getTracks().length) { + self.remoteStreams.push(stream); + window.setTimeout(function() { + self.onaddstream({stream: stream}); + }, 0); + } + }, 0); + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return new Promise(function(resolve) { + resolve(); + }); + }; + + window.RTCPeerConnection.prototype.close = function() { + this.mLines.forEach(function(mLine) { + /* not yet + if (mLine.iceGatherer) { + mLine.iceGatherer.close(); + } + */ + if (mLine.iceTransport) { + mLine.iceTransport.stop(); + } + if (mLine.dtlsTransport) { + mLine.dtlsTransport.stop(); + } + if (mLine.rtpSender) { + mLine.rtpSender.stop(); + } + if (mLine.rtpReceiver) { + mLine.rtpReceiver.stop(); + } + }); + // FIXME: clean up tracks, local streams, remote streams, etc + this._updateSignalingState('closed'); + this._updateIceConnectionState('closed'); + }; + + // Update the signaling state. + window.RTCPeerConnection.prototype._updateSignalingState = + function(newState) { + this.signalingState = newState; + if (this.onsignalingstatechange !== null) { + this.onsignalingstatechange(); + } + }; + + // Update the ICE connection state. + // FIXME: should be called 'updateConnectionState', also be called for + // DTLS changes and implement + // https://lists.w3.org/Archives/Public/public-webrtc/2015Sep/0033.html + window.RTCPeerConnection.prototype._updateIceConnectionState = + function(newState) { + var self = this; + if (this.iceConnectionState !== newState) { + var agreement = self.mLines.every(function(mLine) { + return mLine.iceTransport.state === newState; + }); + if (agreement) { + self.iceConnectionState = newState; + if (this.oniceconnectionstatechange !== null) { + this.oniceconnectionstatechange(); + } + } + } + }; + + window.RTCPeerConnection.prototype.createOffer = function() { + var self = this; + var offerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + offerOptions = arguments[0]; + } else if (arguments.length === 3) { + offerOptions = arguments[2]; + } + + var tracks = []; + var numAudioTracks = 0; + var numVideoTracks = 0; + // Default to sendrecv. + if (this.localStreams.length) { + numAudioTracks = this.localStreams[0].getAudioTracks().length; + numVideoTracks = this.localStreams[0].getAudioTracks().length; + } + // Determine number of audio and video tracks we need to send/recv. + if (offerOptions) { + // Deal with Chrome legacy constraints... + if (offerOptions.mandatory) { + if (offerOptions.mandatory.OfferToReceiveAudio) { + numAudioTracks = 1; + } else if (offerOptions.mandatory.OfferToReceiveAudio === false) { + numAudioTracks = 0; + } + if (offerOptions.mandatory.OfferToReceiveVideo) { + numVideoTracks = 1; + } else if (offerOptions.mandatory.OfferToReceiveVideo === false) { + numVideoTracks = 0; + } + } else { + if (offerOptions.offerToReceiveAudio !== undefined) { + numAudioTracks = offerOptions.offerToReceiveAudio; + } + if (offerOptions.offerToReceiveVideo !== undefined) { + numVideoTracks = offerOptions.offerToReceiveVideo; + } + } + } + if (this.localStreams.length) { + // Push local streams. + this.localStreams[0].getTracks().forEach(function(track) { + tracks.push({ + kind: track.kind, + track: track, + wantReceive: track.kind === 'audio' ? + numAudioTracks > 0 : numVideoTracks > 0 + }); + if (track.kind === 'audio') { + numAudioTracks--; + } else if (track.kind === 'video') { + numVideoTracks--; + } + }); + } + // Create M-lines for recvonly streams. + while (numAudioTracks > 0 || numVideoTracks > 0) { + if (numAudioTracks > 0) { + tracks.push({ + kind: 'audio', + wantReceive: true + }); + numAudioTracks--; + } + if (numVideoTracks > 0) { + tracks.push({ + kind: 'video', + wantReceive: true + }); + numVideoTracks--; + } + } + + var sdp = 'v=0\r\n' + + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; + var mLines = []; + tracks.forEach(function(mline, sdpMLineIndex) { + // For each track, create an ice gatherer, ice transport, dtls transport, + // potentially rtpsender and rtpreceiver. + var track = mline.track; + var kind = mline.kind; + var mid = Math.random().toString(36).substr(2, 10); + + var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); + + var localCapabilities = RTCRtpSender.getCapabilities(kind); + var rtpSender; + // generate an ssrc now, to be used later in rtpSender.send + var sendSSRC = (2 * sdpMLineIndex + 1) * 1001; //Math.floor(Math.random()*4294967295); + var recvSSRC; // don't know yet + if (track) { + rtpSender = new RTCRtpSender(track, transports.dtlsTransport); + } + + var rtpReceiver; + if (mline.wantReceive) { + rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); + } + + mLines[sdpMLineIndex] = { + iceGatherer: transports.iceGatherer, + iceTransport: transports.iceTransport, + dtlsTransport: transports.dtlsTransport, + localCapabilities: localCapabilities, + remoteCapabilities: null, + rtpSender: rtpSender, + rtpReceiver: rtpReceiver, + kind: kind, + mid: mid, + sendSSRC: sendSSRC, + recvSSRC: recvSSRC + }; + + // Map things to SDP. + // Build the mline. + sdp += 'm=' + kind + ' 9 UDP/TLS/RTP/SAVPF '; + sdp += localCapabilities.codecs.map(function(codec) { + return codec.preferredPayloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += self._iceParametersToSDP( + transports.iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += self._dtlsParametersToSDP( + transports.dtlsTransport.getLocalParameters(), 'actpass'); + + sdp += 'a=mid:' + mid + '\r\n'; + + if (rtpSender && rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (rtpSender) { + sdp += 'a=sendonly\r\n'; + } else if (rtpReceiver) { + sdp += 'a=recvonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + sdp += 'a=rtcp-mux\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + sdp += self._capabilitiesToSDP(localCapabilities); + + if (track) { + sdp += 'a=msid:' + self.localStreams[0].id + ' ' + track.id + '\r\n'; + sdp += 'a=ssrc:' + sendSSRC + ' ' + 'msid:' + + self.localStreams[0].id + ' ' + track.id + '\r\n'; + } + sdp += 'a=ssrc:' + sendSSRC + ' cname:' + self._cname + '\r\n'; + }); + + var desc = new RTCSessionDescription({ + type: 'offer', + sdp: sdp, + ortc: mLines + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return new Promise(function(resolve) { + resolve(desc); + }); + }; + + window.RTCPeerConnection.prototype.createAnswer = function() { + var self = this; + var answerOptions; + if (arguments.length === 1 && typeof arguments[0] !== 'function') { + answerOptions = arguments[0]; + } else if (arguments.length === 3) { + answerOptions = arguments[2]; + } + + var sdp = 'v=0\r\n' + + 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' + + 's=-\r\n' + + 't=0 0\r\n'; + this.mLines.forEach(function(mLine/*, sdpMLineIndex*/) { + var iceGatherer = mLine.iceGatherer; + //var iceTransport = mLine.iceTransport; + var dtlsTransport = mLine.dtlsTransport; + var localCapabilities = mLine.localCapabilities; + var remoteCapabilities = mLine.remoteCapabilities; + var rtpSender = mLine.rtpSender; + var rtpReceiver = mLine.rtpReceiver; + var kind = mLine.kind; + var sendSSRC = mLine.sendSSRC; + //var recvSSRC = mLine.recvSSRC; + + // Calculate intersection of capabilities. + var commonCapabilities = self._getCommonCapabilities(localCapabilities, + remoteCapabilities); + + // Map things to SDP. + // Build the mline. + sdp += 'm=' + kind + ' 9 UDP/TLS/RTP/SAVPF '; + sdp += commonCapabilities.codecs.map(function(codec) { + return codec.payloadType; + }).join(' ') + '\r\n'; + + sdp += 'c=IN IP4 0.0.0.0\r\n'; + sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; + + // Map ICE parameters (ufrag, pwd) to SDP. + sdp += self._iceParametersToSDP(iceGatherer.getLocalParameters()); + + // Map DTLS parameters to SDP. + sdp += self._dtlsParametersToSDP(dtlsTransport.getLocalParameters(), + 'active'); + + sdp += 'a=mid:' + mLine.mid + '\r\n'; + + if (rtpSender && rtpReceiver) { + sdp += 'a=sendrecv\r\n'; + } else if (rtpReceiver) { + sdp += 'a=sendonly\r\n'; + } else if (rtpSender) { + sdp += 'a=sendonly\r\n'; + } else { + sdp += 'a=inactive\r\n'; + } + sdp += 'a=rtcp-mux\r\n'; + + // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. + sdp += self._capabilitiesToSDP(commonCapabilities); + + if (rtpSender) { + // add a=ssrc lines from RTPSender + sdp += 'a=msid:' + self.localStreams[0].id + ' ' + + rtpSender.track.id + '\r\n'; + sdp += 'a=ssrc:' + sendSSRC + ' ' + 'msid:' + + self.localStreams[0].id + ' ' + rtpSender.track.id + '\r\n'; + } + sdp += 'a=ssrc:' + sendSSRC + ' cname:' + self._cname + '\r\n'; + }); + + var desc = new RTCSessionDescription({ + type: 'answer', + sdp: sdp + // ortc: tracks -- state is created in SRD already + }); + if (arguments.length && typeof arguments[0] === 'function') { + window.setTimeout(arguments[0], 0, desc); + } + return new Promise(function(resolve) { + resolve(desc); + }); + }; + + window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) { + // TODO: lookup by mid + var mLine = this.mLines[candidate.sdpMLineIndex]; + if (mLine) { + var cand = Object.keys(candidate.candidate).length > 0 ? + this._toCandidateJSON(candidate.candidate) : {}; + // dirty hack to make simplewebrtc work. + // FIXME: need another dirty hack to avoid adding candidates after this + if (cand.type === 'endOfCandidates') { + cand = {}; + } + // dirty hack to make chrome work. + if (cand.protocol === 'tcp' && cand.port === 0) { + cand = {}; + } + mLine.iceTransport.addRemoteCandidate(cand); + } + if (arguments.length > 1 && typeof arguments[1] === 'function') { + window.setTimeout(arguments[1], 0); + } + return new Promise(function(resolve) { + resolve(); + }); + }; + + window.RTCPeerConnection.prototype.getStats = function() { + var promises = []; + this.mLines.forEach(function(mLine) { + ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', + 'dtlsTransport'].forEach(function(thing) { + if (mLine[thing]) { + promises.push(mLine[thing].getStats()); + } + }); + }); + var cb = arguments.length > 1 && typeof arguments[1] === 'function' && + arguments[1]; + return new Promise(function(resolve) { + var results = {}; + Promise.all(promises).then(function(res) { + res.forEach(function(result) { + Object.keys(result).forEach(function(id) { + results[id] = result[id]; + }); + }); + if (cb) { + window.setTimeout(cb, 0, results); + } + resolve(results); + }); + }); + }; + } +} else { + webrtcUtils.log('Browser does not appear to be WebRTC-capable'); +} + +// Returns the result of getUserMedia as a Promise. +function requestUserMedia(constraints) { + return new Promise(function(resolve, reject) { + getUserMedia(constraints, resolve, reject); + }); +} + +var webrtcTesting = {}; +Object.defineProperty(webrtcTesting, 'version', { + set: function(version) { + webrtcDetectedVersion = version; + } +}); + +if (typeof module !== 'undefined') { + var RTCPeerConnection; + if (typeof window !== 'undefined') { + RTCPeerConnection = window.RTCPeerConnection; + } + module.exports = { + RTCPeerConnection: RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting + //requestUserMedia: not exposed on purpose. + }; +} else if ((typeof require === 'function') && (typeof define === 'function')) { + // Expose objects and functions when RequireJS is doing the loading. + define([], function() { + return { + RTCPeerConnection: window.RTCPeerConnection, + getUserMedia: getUserMedia, + attachMediaStream: attachMediaStream, + reattachMediaStream: reattachMediaStream, + webrtcDetectedBrowser: webrtcDetectedBrowser, + webrtcDetectedVersion: webrtcDetectedVersion, + webrtcMinimumVersion: webrtcMinimumVersion, + webrtcTesting: webrtcTesting + //requestUserMedia: not exposed on purpose. + }; + }); +} diff --git a/samples/screen-sharing/js/lib/bootstrap.min.js b/samples/screen-sharing/js/lib/bootstrap.min.js new file mode 100644 index 0000000..9bcd2fc --- /dev/null +++ b/samples/screen-sharing/js/lib/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.7 (http://getbootstrap.com) + * Copyright 2011-2016 Twitter, Inc. + * Licensed under the MIT license + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); \ No newline at end of file diff --git a/samples/screen-sharing/js/lib/jain-sip.js b/samples/screen-sharing/js/lib/jain-sip.js new file mode 100644 index 0000000..919d9e7 --- /dev/null +++ b/samples/screen-sharing/js/lib/jain-sip.js @@ -0,0 +1,32108 @@ +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP GenericObject class. + * @see gov/nist/core/GenericObject.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ + + + Array.prototype.remove = function(from, to) { + var rest = this.slice((to || from) + 1 || this.length); + this.length = from < 0 ? this.length + from : from; + return this.push.apply(this, rest); + }; + + if(!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g,''); + }; + } + + if(!String.prototype.endsWith) { + String.prototype.endsWith = function(suffix) { + return this.indexOf(suffix, this.length - suffix.length) !== -1; + }; + } + +function GenericObject() { + this.classname="GenericObject"; +} + +GenericObject.prototype.SEMICOLON = ";"; +GenericObject.prototype.COLON = ":"; +GenericObject.prototype.COMMA = ","; +GenericObject.prototype.SLASH = "/"; +GenericObject.prototype.SP = " "; +GenericObject.prototype.EQUALS = "="; +GenericObject.prototype.STAR = "*"; +GenericObject.prototype.NEWLINE = "\r\n"; +GenericObject.prototype.RETURN = "\n"; +GenericObject.prototype.LESS_THAN = "<"; +GenericObject.prototype.GREATER_THAN = ">"; +GenericObject.prototype.AT = "@"; +GenericObject.prototype.DOT = "."; +GenericObject.prototype.QUESTION = "?"; +GenericObject.prototype.POUND = "#"; +GenericObject.prototype.AND = "&"; +GenericObject.prototype.LPAREN = "("; +GenericObject.prototype.RPAREN = ")"; +GenericObject.prototype.DOUBLE_QUOTE = "\""; +GenericObject.prototype.QUOTE = "\'"; +GenericObject.prototype.HT = "\t"; +GenericObject.prototype.PERCENT = "%"; + +GenericObject.prototype.getClassFromName =function(className){ + function class_for_name(name) { + return new Function('return new ' + name)(); + } + var classfromname=class_for_name(className); + return classfromname; +} + +GenericObject.prototype.isMySubclass=function(other){ + if((typeof other)!="object"||other instanceof Array) + { + return false; + } + else + { + var c=0; + if(Object.getPrototypeOf(other).classname=="GenericObject") + { + return true; + } + else + { + O=Object.getPrototypeOf(other); + for(;O.classname!=undefined;) + { + if(Object.getPrototypeOf(O).classname=="GenericObject") + { + c=1; + O=Object.getPrototypeOf(O); + } + else + { + O=Object.getPrototypeOf(O); + } + } + if(c==1) + { + return true; + } + else + { + return false; + } + + } + } +} + +GenericObject.prototype.encode=function(buffer){ + return buffer+this.encode(); +} + +GenericObject.prototype.equals=function(that){ +} + +GenericObject.prototype.match=function(other){ +} + +GenericObject.prototype.merge=function(mergeObject){ +} + +GenericObject.prototype.clone=function(){ + var objClone; + if (this.constructor == Object){ + objClone = new this.constructor(); + }else{ + objClone = new this.constructor(this.valueOf()); + } + for(var key in this){ + if ( objClone[key] != this[key] ){ + if ( typeof(this[key]) == 'object' ){ + objClone[key] = this[key].clone(); + }else{ + objClone[key] = this[key]; + } + } + } + objClone.toString = this.toString; + objClone.valueOf = this.valueOf; + return objClone; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP GenericObjectList class. + * @see gov/nist/core/GenericObjectList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function GenericObjectList() { + this.classname="GenericObjectList"; +} + +GenericObjectList.prototype.isMySubclass=function(other){ + if((typeof other)!="object"||other instanceof Array) + { + return false; + } + else + { + var c=0; + if(Object.getPrototypeOf(other).classname=="GenericObjectList") + { + return true; + } + else + { + O=Object.getPrototypeOf(other); + for(;O.classname!=undefined;) + { + if(Object.getPrototypeOf(O).classname=="GenericObjectList") + { + c=1; + O=Object.getPrototypeOf(O); + } + else + { + O=Object.getPrototypeOf(O); + } + } + if(c==1) + { + return true; + } + else + { + return false; + } + + } + } +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP NameValue class. + * @see gov/nist/core/NameValue.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function NameValue(n,v,isFlag) { + this.classname="NameValue"; + this.isQuotedString = null; + this.isFlagParameter= isFlag; + this.separator = this.EQUALS; + this.quotes= ""; + this.name = n; + this.value= v; + if(n==null) + { + this.name=null; + } + if(v==null) + { + this.value=null; + } + if(isFlag==null) + { + this.isFlagParameter=false; + } +} + +NameValue.prototype = new GenericObject(); +NameValue.prototype.constructor=NameValue; +NameValue.prototype.EQUALS="="; +NameValue.prototype.DOUBLE_QUOTE="\""; + +NameValue.prototype.setSeparator=function(sep){ + this.separator=sep; +} + +NameValue.prototype.setQuotedValue=function(){ + this.isQuotedString=true; + this.quotes=this.DOUBLE_QUOTE; +} + +NameValue.prototype.isValueQuoted=function(){ + return this.isQuotedString; +} + +NameValue.prototype.getName=function(){ + return this.name; +} + +NameValue.prototype.getValueAsObject=function(){ + return this.isFlagParameter ? "" : this.value; +} + +NameValue.prototype.setName=function(n){ + this.name=n; +} + +NameValue.prototype.setValueAsObject=function(v){ + this.value=v; +} + +NameValue.prototype.encode=function(){ + return this.encodeBuffer("").toString(); +} + +NameValue.prototype.encodeBuffer=function(buffer){ + var go=new GenericObject(); + var gol=new GenericObjectList(); + + if (this.name != null && this.value != null && !this.isFlagParameter) { + + if (go.isMySubclass(this.value)) { + buffer=buffer+this.name+this.separator+this.quotes; + buffer=this.value.encodeBuffer(buffer); + buffer=buffer+this.quotes; + return buffer; + } else if (gol.isMySubclass(this.value)) { + buffer=buffer+this.name+this.separator+this.value.encode(); + return buffer; + } else if (this.value.toString().length == 0) { + // opaque="" bug fix - pmusgrave + /* + * if + * (name.toString().equals(gov.nist.javax.sip.header.ParameterNames.OPAQUE)) + * return name + separator + quotes + quotes; else return name; + */ + if (this.isQuotedString) { + buffer=buffer+this.name+this.separator+this.quotes+this.quotes; + return buffer; + } else { + if(this.name=="lr")//when it is lr, we don't need "=" + { + buffer=buffer+this.name; + return buffer; + } + else + { + buffer=buffer+this.name+this.separator; + //buffer.append(name).append(separator); // JvB: fix, case: "sip:host?subject=" + return buffer; + } + + } + } else { + buffer=buffer+this.name+this.separator+this.quotes+this.value.toString()+this.quotes; + return buffer; + } + } else if (this.name == null && this.value != null) { + + if (go.isMySubclass(this.value)) { + this.value.encodeBuffer(buffer); + return buffer; + } else if (gol.isMySubclass(this.value)) { + buffer=buffer+this.value.encode(); + return buffer; + } else { + buffer=buffer+this.quotes+this.value.toString()+this.quotes; + return buffer; + } + } else if (this.name != null && (this.value == null || this.isFlagParameter)) { + buffer=buffer+this.name; + return buffer; + } else { + return buffer; + } +} + +NameValue.prototype.equals=function(other){ + if (other == null ) return false; + if (other.classname!=this.classname) + { + return false; + } + var that = other; + if (this == that) + { + return true; + } + if (this.name == null && that.name != null || this.name != null + && that.name == null) + { + return false; + } + if (this.name != null && that.name != null + && this.name.compareToIgnoreCase(that.name) != 0) + { + return false; + } + if (this.value != null && that.value == null || this.value == null + && that.value != null) + { + return false; + } + if (this.value == that.value) + { + return true; + } + if (this.value instanceof String) { + // Quoted string comparisions are case sensitive. + if (this.isQuotedString) + { + if(this.value==that.value) + { + return true; + } + else + { + return false; + } + } + var val = this.value; + var val1 = that.value; + if(val.toLowerCase()==val1.toLowerCase()) + { + return true; + } + else + { + return false; + } + } + else + { + if(this.value==that.value) + { + return true; + } + else + { + return false; + } + } +} + +NameValue.prototype.getKey=function(){ + return this.name; +} + +NameValue.prototype.getValue=function(){ + return this.value == null ? null : this.value.toString(); +} + +NameValue.prototype.setValue=function(value){ + var retval = this.value == null ? null : value; + this.value = value; + return retval; +} + +NameValue.prototype.hashCode=function(){ + var hash = 0; + var x=this.encode().toLowerCase(); + if(!(x == null || x.value == "")) + { + for (var i = 0; i < x.length; i++) + { + hash = hash * 31 + x.charCodeAt(i); + var MAX_VALUE = 0x7fffffff; + var MIN_VALUE = -0x80000000; + if(hash > MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP NameValueList class. + * @see gov/nist/core/NameValueList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function NameValueList(sync) { + this.classname="NameValueList"; + this.hmap = new Array(); + this.separator=";"; + +} + +NameValueList.prototype.setSeparator =function(separator){ + this.separator=separator; +} + +NameValueList.prototype.encode =function(){ + return this.encodeBuffer("").toString(); +} + +NameValueList.prototype.encodeBuffer =function(buffer){ + if (this.hmap.length!=0) + { + for(var i=0;i MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + + + /* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP DuplicateNameValueList class. + * @see gov/nist/core/DuplicateNameValueList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function DuplicateNameValueList() { + this.classname="DuplicateNameValueList"; + this.nameValueMap = new Array(); + this.separator=";"; +} + +DuplicateNameValueList.prototype.setSeparator =function(separator){ + this.separator=separator; +} + +DuplicateNameValueList.prototype.encode =function(){ + return this.encodeBuffer("").toString(); +} + +DuplicateNameValueList.prototype.encodeBuffer =function(buffer){ + if (this.nameValueMap.length!=0) + { + for(var i=0;i MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP HostPort class. + * @see gov/nist/core/HostPort.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function HostPort() { + this.classname="HostPort"; + this.port = -1; + this.host=new Host(); +} + +HostPort.prototype = new GenericObject(); +HostPort.prototype.constructor=HostPort; +HostPort.prototype.COLON=":"; + +HostPort.prototype.encode =function(){ + return this.encodeBuffer(""); +} +HostPort.prototype.encodeBuffer =function(buffer){ + buffer=this.host.encodeBuffer(buffer); + if (this.port != -1) + { + buffer=buffer+this.COLON+this.port; + } + return buffer; +} +HostPort.prototype.equals =function(other){ + if (other == null) { + return false; + } + if (this.classname!=other.classname) { + return false; + } + var that = other; + if(this.port == that.port && this.host.equals(that.host)) + { + return true; + } + else + { + return false; + } +} + +HostPort.prototype.getHost =function(){ + return this.host; +} + +HostPort.prototype.getPort =function(){ + return this.port; +} + +HostPort.prototype.hasPort =function(){ + if(this.port!=-1) + { + return true; + } + else + { + return false; + } +} + +HostPort.prototype.removePort =function(){ + this.port=-1; +} + +HostPort.prototype.setHost =function(h){ + this.host=h; +} + +HostPort.prototype.setPort =function(p){ + this.port=p; +} + +HostPort.prototype.getInetAddress =function(){ + if (this.host == null) + { + return null; + } + else + { + return this.host.getInetAddress(); + } +} + +HostPort.prototype.merge =function(mergeObject){ + var go=new GenericObject(); + go.merge (mergeObject); + if (this.port == -1) + { + this.port = mergeObject.port; + } +} + + +HostPort.prototype.toString =function(){ + return this.encode(); +} + +HostPort.prototype.hashCode =function(){ + return this.host.hashCode()+this.port; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Host class. + * @see gov/nist/core/Host.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ + +function Host(hn,addresstype) { + this.stripAddressScopeZones = false; + this.hostname = null; + this.addressType=null; + this.inetAddress = null; + this.classname="Host"; + if(hn==null&&addresstype==null) + { + this.addressType = this.HOSTNAME; + } + else if(addresstype==null&&hn!=null) + { + this.setHost(hn, this.IPV4ADDRESS); + } + else + { + this.setHost(hn, addresstype); + } +} + +Host.prototype = new GenericObject(); +Host.prototype.constructor=Host; +Host.prototype.HOSTNAME = 1; +Host.prototype.IPV4ADDRESS = 2; +Host.prototype.IPV6ADDRESS = 3; + +Host.prototype.encode =function(){ + return this.encodeBuffer("").toString(); +} + +Host.prototype.encodeBuffer =function(buffer){ + var encode=null; + if (this.addressType == this.IPV6ADDRESS && !this.isIPv6Reference(this.hostname)) { + encode=buffer+"["+this.hostname+"]"; + } else { + encode=buffer+this.hostname; + } + return encode; +} + +/** + * Compare for equality of hosts. + * Host names are compared by textual equality. No dns lookup + * is performed. + * @param obj Object to set + * @return boolean + */ +Host.prototype.equals =function(otherHost){ + if ( otherHost == null ) + { + return false; + } + if (this.classname!=otherHost.classname) { + return false; + } + if(otherHost.hostname==this.hostname) + { + return true; + } + else + { + return false; + } +} + +Host.prototype.getHostname =function(){ + return this.hostname; +} + +Host.prototype.getAddress =function(){ + return this.hostname; +} + +Host.prototype.getIpAddress =function(){////////////////////////////////////////problem dans cette méthode + var rawIpAddress = null; + if (this.hostname == null) + return null; + if (this.addressType == this.HOSTNAME) { + if (this.inetAddress == null) + // CAN NOT BE IMPLEMENTED + /*this.inetAddress = InetAddress.getByName(hostname); + rawIpAddress = inetAddress.getHostAddress();*/; + } else { + rawIpAddress = this.hostname; + } + return rawIpAddress; +} + + +Host.prototype.setHostname =function(h){ + this.setHost(h, this.HOSTNAME); +} + +Host.prototype.setHostAddress =function(address){ + this.setHost(address, this.IPV4ADDRESS); +} + +Host.prototype.setHost =function(host,type){ + this.inetAddress = null; + if(type==null) + { + type=2; + } + if (this.isIPv6Address(host)) + { + this.addressType = this.IPV6ADDRESS; + } + else + { + this.addressType = type; + } + if (host != null){ + this.hostname=host.trim(); + if(this.addressType == this.HOSTNAME) + { + this.hostname = this.hostname.toLowerCase(); + } + var zoneStart = -1; + if(this.addressType == this.IPV6ADDRESS + && this.stripAddressScopeZones + && (zoneStart = this.hostname.indexOf('%'))!= -1) + { + this.hostname = this.hostname.substring(0, zoneStart); + } + } +} + +Host.prototype.setAddress =function(address){ + this.setHostAddress(address); +} + +Host.prototype.isHostname =function(){ + if (this.addressType == this.HOSTNAME) { + return true; + } else { + return false; + } +} + + +Host.prototype.isIPAddress =function(){ + if (this.addressType != this.HOSTNAME) { + return true; + } else { + return false; + } +} + +Host.prototype.getInetAddress =function(){ + if (this.hostname == null) + { + return null; + } + if (this.inetAddress != null) + { + return this.inetAddress; + } + // CAN NOT BE IMPLEMENTED + /*this.inetAddress = InetAddress.getByName(hostname);*//////////////////////meme problem comme la méthode d'avant' + return this.inetAddress; +} + +//----- IPv6 +Host.prototype.isIPv6Address =function(address){ + if (address != null && address.indexOf(':') != -1) { + return true; + } else { + return false; + } +} + +Host.prototype.isIPv6Reference =function(address){ + if (address.charAt(0) == '[' + && address.charAt(address.length() - 1) == ']') { + return true; + } else { + return false; + } +} + +Host.prototype.hashCode =function(){ + var hash = 0; + var x=this.getHostname(); + if(!(x == null || x.value == "")) + { + for (var i = 0; i < x.length; i++) + { + hash = hash * 31 + x.charCodeAt(i); + var MAX_VALUE = 0x7fffffff; + var MIN_VALUE = -0x80000000; + if(hash > MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Token class. + * @see gov/nist/core/Token.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Token() { + this.classname="Token"; + this.tokenValue=null; + this.tokenType=null; +} + +Token.prototype.getTokenValue =function(){ + return this.tokenValue; +} + +Token.prototype.getTokenType =function(){ + return this.tokenType; +} + +Token.prototype.toString =function(){ + return "tokenValue = " + this.tokenValue + "/tokenType = " + this.tokenType; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP StringTokenizer class. + * @see gov/nist/core/StringTokenizer.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function StringTokenizer(buffer) { + this.classname="StringTokenizer"; + this.buffer=null; + this.bufferLen=null; + this.ptr=null; + this.savedPtr=null; + if(buffer!=null) + { + this.buffer = buffer; + this.bufferLen = buffer.length; + this.ptr = 0; + } +} + +StringTokenizer.prototype.nextToken =function(){ + var startIdx = this.ptr; + while (this.ptr < this.bufferLen) { + var c = this.buffer.charAt(this.ptr); + this.ptr++; + if (c == '\n') { + break; + } + } + return this.buffer.substring(startIdx, this.ptr); +} + +StringTokenizer.prototype.hasMoreChars =function(){ + if(this.ptr < this.bufferLen&&this.buffer.charAt(this.ptr)!='\r') + { + return true; + } + else + { + return false; + } +} + +StringTokenizer.prototype.isHexDigit =function(ch){ + if((ch >= "A" && ch <= "F") + || (ch >= "a" && ch <= "f") + || this.isDigit(ch)) + { + return true; + } + else + { + return false; + } +} + +StringTokenizer.prototype.isAlpha =function(ch){ + if(ch.charCodeAt(0) <= 127) + { + if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) + + { + return true + } + else + { + return false; + } + } + else + { + if(ch==ch.toLowerCase()||ch==ch.toUpperCase()) + { + return true; + } + else + { + return false; + } + } +} + +StringTokenizer.prototype.isDigit =function(ch){ + if(ch.charCodeAt(0) <= 127) + { + if(ch <= '9' && ch >= '0') + + { + return true + } + else + { + return false; + } + } + else + { + if(typeof ch=="number") + { + return true; + } + else + { + return false; + } + } +} + +StringTokenizer.prototype.isAlphaDigit =function(ch){ + if(ch.charCodeAt(0) <= 127) + { + if((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')||(ch <= '9' && ch >= '0')) + + { + return true + } + else + { + return false; + } + } + else + { + if((ch==ch.toLowerCase())||(ch==ch.toUpperCase())||(typeof ch=="number")) + { + return true; + } + else + { + return false; + } + } +} + +StringTokenizer.prototype.getLine =function(){ + var startIdx = this.ptr; + while (this.ptr < this.bufferLen && this.buffer.charAt(this.ptr) != '\n') { + this.ptr++; + } + if (this.ptr < this.bufferLen && this.buffer.charAt(this.ptr) == '\n') { + this.ptr++; + } + return this.buffer.substring(startIdx, this.ptr); +} + +StringTokenizer.prototype.peekLine =function(){ + var curPos = this.ptr; + var retval = this.getLine(); + this.ptr = curPos; + return retval; +} + +StringTokenizer.prototype.lookAhead =function(k){ + if(k==null) + { + k=0; + } + return this.buffer.charAt(this.ptr + k); +} + +StringTokenizer.prototype.getNextChar =function(){ + if (this.ptr >= this.bufferLen) { + console.error("StringTokenizer:getNextChar(): end of buffer:"+this.ptr); + throw "StringTokenizer:getNextChar(): end of buffer"; + } + else + { + return this.buffer.charAt(this.ptr++); + } +} + +StringTokenizer.prototype.consume =function(k){ + if(k==null) + { + this.ptr = this.savedPtr; + } + else + { + this.ptr += k; + } +} + +StringTokenizer.prototype.getLines =function(){ + var result = new Array(); + while (this.hasMoreChars()) { + var line = this.getLine(); + result.push(line); + } + return result; +} + +StringTokenizer.prototype.getNextToken =function(delim){ + var startIdx = this.ptr; + while (true) { + var la = this.lookAhead(0); + if (la == delim) + { + break; + } + else if (la == '') + { + console.error("StringTokenizer:getNextToken(): EOL reached"); + throw "StringTokenizer:getNextToken(): EOL reached"; + } + this.consume(1); + } + return this.buffer.substring(startIdx, this.ptr); +} + +StringTokenizer.prototype.getSDPFieldName =function(line){ + if (line == null) { + return null; + } + var fieldName = null; + var begin = line.indexOf("="); + fieldName = line.substring(0, begin); + return fieldName; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP LexerCore class. + * @see gov/nist/core/LexerCore.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @todo catch exception + */ +function LexerCore() { + this.classname="LexerCore"; + this.globalSymbolTable=new Array(); + this.lexerTables=new Array(); + this.currentLexer=null; + this.currentLexerName=null; + this.currentMatch=new Token(); + if(arguments.length==0) + { + this.currentLexer = new Array(); + this.currentLexerName = "charLexer"; + } + else + { + var lexerName=arguments[0]; + var buffer=arguments[1]; + this.buffer = buffer; + this.bufferLen = buffer.length; + this.ptr = 0; + this.currentLexer = new Array(); + this.currentLexerName = lexerName; + } +} + +LexerCore.prototype = new StringTokenizer(); +LexerCore.prototype.constructor=LexerCore; +LexerCore.prototype.START = 2048; +LexerCore.prototype.END = LexerCore.prototype.START + 2048; +LexerCore.prototype.ID = LexerCore.prototype.END - 1; +LexerCore.prototype.SAFE = LexerCore.prototype.END - 2; +LexerCore.prototype.ID_NO_WHITESPACE = LexerCore.prototype.END - 3; +LexerCore.prototype.WHITESPACE = LexerCore.prototype.END + 1; +LexerCore.prototype.DIGIT = LexerCore.prototype.END + 2; +LexerCore.prototype.ALPHA = LexerCore.prototype.END + 3; +LexerCore.prototype.BACKSLASH = "\\".charCodeAt(0); +LexerCore.prototype.QUOTE = "\'".charCodeAt(0); +LexerCore.prototype.AT = "@".charCodeAt(0); +LexerCore.prototype.SP = " ".charCodeAt(0); +LexerCore.prototype.HT = "\t".charCodeAt(0); +LexerCore.prototype.COLON = ":".charCodeAt(0); +LexerCore.prototype.STAR = "*".charCodeAt(0); +LexerCore.prototype.DOLLAR = "$".charCodeAt(0); +LexerCore.prototype.PLUS = "+".charCodeAt(0); +LexerCore.prototype.POUND = "#".charCodeAt(0); +LexerCore.prototype.MINUS = "-".charCodeAt(0); +LexerCore.prototype.DOUBLEQUOTE = "\"".charCodeAt(0); +LexerCore.prototype.TILDE = "~".charCodeAt(0); +LexerCore.prototype.BACK_QUOTE = "`".charCodeAt(0); +LexerCore.prototype.NULL = "\0".charCodeAt(0); +LexerCore.prototype.EQUALS = "=".charCodeAt(0); +LexerCore.prototype.SEMICOLON = ";".charCodeAt(0); +LexerCore.prototype.SLASH = "/".charCodeAt(0); +LexerCore.prototype.L_SQUARE_BRACKET = "[".charCodeAt(0); +LexerCore.prototype.R_SQUARE_BRACKET = "]".charCodeAt(0); +LexerCore.prototype.R_CURLY = "}".charCodeAt(0); +LexerCore.prototype.L_CURLY = "{".charCodeAt(0); +LexerCore.prototype.HAT = "^".charCodeAt(0); +LexerCore.prototype.BAR = "|".charCodeAt(0); +LexerCore.prototype.DOT = ".".charCodeAt(0); +LexerCore.prototype.EXCLAMATION = "!".charCodeAt(0); +LexerCore.prototype.LPAREN = "(".charCodeAt(0); +LexerCore.prototype.RPAREN = ")".charCodeAt(0); +LexerCore.prototype.GREATER_THAN = ">".charCodeAt(0); +LexerCore.prototype.LESS_THAN = "<".charCodeAt(0); +LexerCore.prototype.PERCENT = "%".charCodeAt(0); +LexerCore.prototype.QUESTION = "?".charCodeAt(0); +LexerCore.prototype.AND = "&".charCodeAt(0); +LexerCore.prototype.UNDERSCORE = "_".charCodeAt(0); +LexerCore.prototype.ALPHA_VALID_CHARS = String.fromCharCode(65535); +LexerCore.prototype.DIGIT_VALID_CHARS = String.fromCharCode(65534); +LexerCore.prototype.ALPHADIGIT_VALID_CHARS = String.fromCharCode(65533); + +LexerCore.prototype.addKeyword =function(name, value){ + var val = value; + this.currentLexer=this.put(this.currentLexer, name, val); + var j=null + for(var i=0;i this.START) { + for(var i=0;i this.START && tok < this.END) { + if (tok == this.ID) { + if (!this.startsId()) { + console.error("LexerCore:match(): "+ this.buffer + "\nID expected", this.ptr); + throw "LexerCore:match(): ID expected"; + } + var id = this.getNextId(); + this.currentMatch = new Token(); + this.currentMatch.tokenValue = id; + this.currentMatch.tokenType = this.ID; + } else if (tok == this.SAFE) { + if (!this.startsSafeToken()) { + console.error("LexerCore:match(): "+ this.buffer + "\nID expected", this.ptr); + throw "LexerCore:match(): ID expected"; + } + id = this.ttokenSafe(); + this.currentMatch = new Token(); + this.currentMatch.tokenValue = id; + this.currentMatch.tokenType = this.SAFE; + } else { + var nexttok = this.getNextId(); + var n=null; + for(var i=0;i this.END) { + var next = this.lookAhead(0); + if (tok == this.DIGIT) { + if (!this.isDigit(next)) { + console.error("LexerCore:match(): "+ this.buffer + "\nExpecting DIGIT", this.ptr); + throw "LexerCore:match(): Expecting DIGIT"; + } + this.currentMatch = new Token(); + this.currentMatch.tokenValue =next; + this.currentMatch.tokenType = tok; + this.consume(1); + + } else if (tok == this.ALPHA) { + if (!this.isAlpha(next)) { + console.error("LexerCore:match(): "+ this.buffer + "\nExpecting ALPHA", this.ptr); + throw "LexerCore:match(): Expecting ALPHA"; + } + this.currentMatch = new Token(); + this.currentMatch.tokenValue =next; + this.currentMatch.tokenType = tok; + this.consume(1); + } + }else { + var ch = tok; + next = this.lookAhead(0); + if (next == ch) { + this.consume(1); + } else { + console.error("LexerCore:match(): "+ this.buffer + "\nExpecting >>>" + ch + "<<< got >>>"+ next + "<<<"); + throw "LexerCore:match(): expecting >>>" + ch + "<<< got >>>"+ next + "<<<"; + } + } + return this.currentMatch; +} + +LexerCore.prototype.SPorHT =function(){ + var c = this.lookAhead(0); + while (c == ' ' || c == '\t') { + this.consume(1); + c = this.lookAhead(0); + } +} + +LexerCore.prototype.isTokenChar =function(c){ + if (this.isAlphaDigit(c)) { + return true; + } else { + switch (c) { + case "-": + case ".": + case "!": + case "%": + case "*": + case "_": + case "+": + case "`": + case "\'": + case "~": + return true; + default: + return false; + } + } +} + +LexerCore.prototype.startsId =function(){ + var nextChar = this.lookAhead(0); + return this.isTokenChar(nextChar); +} + +LexerCore.prototype.startsSafeToken =function(){ + var nextChar = lookAhead(0); + if (this.isAlphaDigit(nextChar)) { + return true; + } else { + switch (nextChar) { + case '_': + case '+': + case '-': + case '!': + case '`': + case '\'': + case '.': + case '/': + case '}': + case '{': + case ']': + case '[': + case '^': + case '|': + case '~': + case '%': // bug fix by Bruno Konik, JvB copied here + case '#': + case '@': + case '$': + case ':': + case ';': + case '?': + case '\"': + case '*': + case '=': // Issue 155 on java.net + return true; + default: + return false; + } + } +} +LexerCore.prototype.ttoken =function(){ + var startIdx = this.ptr; + while (this.hasMoreChars()) { + var nextChar = this.lookAhead(0); + if (this.isTokenChar(nextChar)) { + this.consume(1); + } else { + break; + } + } + return this.buffer.substring(startIdx, this.ptr); +} + +LexerCore.prototype.ttokenSafe =function(){ + var startIdx = this.ptr; + while (this.hasMoreChars()) { + var nextChar = this.lookAhead(0); + if (this.isAlphaDigit(nextChar)) { + this.consume(1); + } else { + var isValidChar = false; + switch (nextChar) { + case '_': + case '+': + case '-': + case '!': + case '`': + case '\'': + case '.': + case '/': + case '}': + case '{': + case ']': + case '[': + case '^': + case '|': + case '~': + case '%': // bug fix by Bruno Konik, JvB copied here + case '#': + case '@': + case '$': + case ':': + case ';': + case '?': + case '\"': + case '*': + isValidChar = true; + } + if (isValidChar) { + this.consume(1); + } else { + break; + } + } + } + return this.buffer.substring(startIdx, this.ptr); +} + +LexerCore.prototype.consumeValidChars =function(validChars){ + var validCharsLength = validChars.length; + while (this.hasMoreChars()) { + var nextChar = this.lookAhead(0); + var isValid = false; + for (var i = 0; i < validCharsLength; i++) { + var validChar = validChars[i]; + switch (validChar) { + case this.ALPHA_VALID_CHARS: + isValid = this.isAlpha(nextChar); + break; + case this.DIGIT_VALID_CHARS: + isValid = this.isDigit(nextChar); + break; + case this.ALPHADIGIT_VALID_CHARS: + isValid = this.isAlphaDigit(nextChar); + break; + default: + isValid = nextChar == validChar; + } + if (isValid) { + break; + } + } + if (isValid) { + this.consume(1); + } else { + break; + } + } +} + +LexerCore.prototype.quotedString =function(){ + var startIdx = this.ptr + 1; + if (this.lookAhead(0) != '\"') { + return null; + } + this.consume(1); + while (this.hasMoreChars()) { + var next = this.getNextChar(); + if (next == '\"') { + break; + } else if (next == '') { + console.error("LexerCore:quotedString(): "+ this.buffer + " :unexpected EOL",this.ptr); + throw "LexerCore:quotedString(): unexpected EOL"; + } else if (next == '\\') { + this.consume(1); + } + } + return this.buffer.substring(startIdx, this.ptr - 1); +} + +LexerCore.prototype.comment =function(){ + var retval = ""; + if (this.lookAhead(0) != '(') { + return null; + } + this.consume(1); + while (this.hasMoreChars()) { + var next = this.getNextChar(); + if (next == ')') { + break; + } else if (next == '') { + console.error("LexerCore:comment(): "+ this.buffer + " :unexpected EOL",this.ptr); + throw "LexerCore:comment(): unexpected EOL"; + } else if (next == '\\') { + retval=retval+next; + next = this.getNextChar(); + if (next == '') { + console.error("LexerCore:comment(): "+ this.buffer + " :unexpected EOL",this.ptr); + throw "LexerCore:comment(): unexpected EOL"; + } + retval=retval+next; + } else { + retval=retval+next; + } + } + return retval.toString(); +} + +LexerCore.prototype.byteStringNoSemicolon =function(){ + var retval = ""; + while (this.hasMoreChars()) { + var next = this.lookAhead(0); + if (next == '' || next == '\n' || next == ';' || next == ',') { + break; + } else { + this.consume(1); + retval=retval+next; + } + } + return retval.toString(); +} + +LexerCore.prototype.byteStringNoWhiteSpace =function(){ + var retval = ""; + while (this.hasMoreChars()) { + var next = this.lookAhead(0); + if (next == '' || next == '\n' || next == ' ') { + break; + } else { + this.consume(1); + retval=retval+next; + } + } + return retval.toString(); +} + +LexerCore.prototype.byteStringNoSlash =function(){ + var retval = ""; + while (this.hasMoreChars()) { + var next = this.lookAhead(0); + if (next == '' || next == '\n' || next == '/') { + break; + } else { + this.consume(1); + retval=retval+next; + } + } + return retval.toString(); +} + +LexerCore.prototype.byteStringNoComma =function(){ + var retval = ""; + while (this.hasMoreChars()) { + var next = this.lookAhead(0); + if (next == '\n' || next == ',') { + break; + } else { + this.consume(1); + retval=retval+next; + } + } + return retval.toString(); +} + +LexerCore.prototype.charAsString =function(){ + if(typeof arguments[0]=="string") + { + var ch=arguments[0]; + return ch; + } + else if(typeof arguments[0]=="number") + { + var nchars=arguments[0]; + return this.buffer.substring(this.ptr, this.ptr + nchars); + } +} + +LexerCore.prototype.number =function(){ + var startIdx = this.ptr; + if (!this.isDigit(this.lookAhead(0))) { + console.error(this.buffer + "LexerCore:number(): Unexpected token at " + this.lookAhead(0),this.ptr); + throw "LexerCore:number(): Unexpected token at " + this.lookAhead(0); + } + this.consume(1); + while (this.hasMoreChars()) { + var next = this.lookAhead(0); + if (this.isDigit(next)) { + this.consume(1); + } else { + break; + } + } + return this.buffer.substring(startIdx, this.ptr); +} + +LexerCore.prototype.markInputPosition =function(){ + return this.ptr; +} + +LexerCore.prototype.rewindInputPosition =function(position){ + this.ptr = position; +} + +LexerCore.prototype.getRest =function(){ + if (this.ptr >= this.buffer.length) { + return null; + } else { + return this.buffer.substring(this.ptr); + } +} + +LexerCore.prototype.getString =function(c){ + var retval = ""; + while (this.hasMoreChars()) { + var next = this.lookAhead(0); + if (next == '') { + console.error(this.buffer + "LexerCore:getString(): unexpected EOL",this.ptr); + throw "LexerCore:getString(): unexpected EOL"; + } else if (next == c) { + this.consume(1); + break; + } else if (next == '\\') { + this.consume(1); + var nextchar = this.lookAhead(0); + if (nextchar == '') { + console.error(this.buffer + "LexerCore:getString(): unexpected EOL",this.ptr); + throw "LexerCore:getString(): unexpected EOL"; + } else { + this.consume(1); + retval=retval+nextchar; + } + } else { + this.consume(1); + retval=retval+next; + } + } + return retval.toString(); +} + +LexerCore.prototype.getPtr =function(){ + return this.ptr; +} + +LexerCore.prototype.getBuffer =function(){ + return this.buffer; +} + +LexerCore.prototype.put =function(table,name, value){ + var n=0; + for(var i=0;i': // OK, can appear in headers + case ' ': // OK, allow whitespace + case '\t': + case '\r': + case '\n': + case '/': // e.g. http://[::1]/xyz.html + break; + case '%': + if(this.stripAddressScopeZones){ + break;//OK,allow IPv6 address scope zone + } + default: + if (!allowWS) { + console.error("Parser:hostPort(): "+ this.lexer.getBuffer() +" illegal character in hostname:" + this.lexer.lookAhead(0),this.lexer.getPtr() ); + throw "Parser:hostPort(): error parsing port"; + } + } + } + return hp; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * A JavaScript implementation (a bit modified by Orange)of the RSA Data Security, Inc. MD5 Message + * Digest Algorithm, as defined in RFC 1321. + * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009 + * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet + * Distributed under the BSD License + * See http://pajhome.org.uk/crypt/md5 for more info. + */ + +function MessageDigestAlgorithm() { + this.classname="MessageDigestAlgorithm"; + this.toHex=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']; + this.hexcase=0; +} + +MessageDigestAlgorithm.prototype.calculateResponse =function(username_value,realm_value, + passwd,nonce_value,nc_value,cnonce_value,method,digest_uri_value,entity_body,qop_value){ + var A1 = null; + A1 = username_value + ":" + realm_value + ":" + passwd; + var A2 = null; + if (qop_value == null || qop_value.length == 0|| qop_value=="auth") + { + A2 = method + ":" + digest_uri_value; + } + else + { + if (entity_body == null) + { + entity_body = ""; + } + A2 = method + ":" + digest_uri_value + ":" + this.H(entity_body); + } + var request_digest = null; + if (cnonce_value != null && qop_value != null && nc_value != null && (qop_value=="auth" || qop_value=="auth-int")) + { + request_digest = this.KD(this.H(A1), nonce_value + ":" + nc_value + ":" + cnonce_value + ":"+ qop_value + ":" + this.H(A2)); + } + else { + request_digest = this.KD(this.H(A1), nonce_value + ":" + this.H(A2)); + } + return request_digest; +} + +MessageDigestAlgorithm.prototype.H =function(data){ + return this.md5(data); +} +MessageDigestAlgorithm.prototype.KD =function(secret,data){ + return this.H(secret + ":" + data); +} + +MessageDigestAlgorithm.prototype.toHexString =function(b){ + var pos = 0; + var chaine=""; + var c = new Array(); + for (var i = 0; i < b.length; i++) { + c[pos++] = this.toHex[(b[i] >> 4) & 0x0F]; + chaine=chaine+c[pos]; + c[pos++] = this.toHex[b[i] & 0x0f]; + chaine=chaine+c[pos]; + } + return chaine; +} +MessageDigestAlgorithm.prototype.md5 =function(chaine){ + return this.hex_md5(chaine); +} + +MessageDigestAlgorithm.prototype.hex_md5=function(s) { + return this.rstr2hex(this.rstr_md5(this.str2rstr_utf8(s))); +} + +MessageDigestAlgorithm.prototype.rstr_md5=function(s) +{ + return this.binl2rstr(this.binl_md5(this.rstr2binl(s), s.length * 8)); +} + +MessageDigestAlgorithm.prototype.rstr2hex=function(input) +{ + var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var output = ""; + var x; + for(var i = 0; i < input.length; i++) + { + x = input.charCodeAt(i); + output += hex_tab.charAt((x >>> 4) & 0x0F) + + hex_tab.charAt( x & 0x0F); + } + return output; +} + +MessageDigestAlgorithm.prototype.str2rstr_utf8=function(input) +{ + var output = ""; + var i = -1; + var x, y; + + while(++i < input.length) + { + x = input.charCodeAt(i); + y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; + if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) + { + x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); + i++; + } + + if(x <= 0x7F) + output += String.fromCharCode(x); + else if(x <= 0x7FF) + output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), + 0x80 | ( x & 0x3F)); + else if(x <= 0xFFFF) + output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + else if(x <= 0x1FFFFF) + output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), + 0x80 | ((x >>> 12) & 0x3F), + 0x80 | ((x >>> 6 ) & 0x3F), + 0x80 | ( x & 0x3F)); + } + return output; +} + +MessageDigestAlgorithm.prototype.rstr2binl=function(input) +{ + var output = Array(input.length >> 2); + for(var i = 0; i < output.length; i++) + output[i] = 0; + for(i = 0; i < input.length * 8; i += 8) + output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32); + return output; +} + +MessageDigestAlgorithm.prototype.binl2rstr=function(input) +{ + var output = ""; + for(var i = 0; i < input.length * 32; i += 8) + output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF); + return output; +} + +MessageDigestAlgorithm.prototype.binl_md5=function(x, len) +{ + x[len >> 5] |= 0x80 << ((len) % 32); + x[(((len + 64) >>> 9) << 4) + 14] = len; + + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + + for(var i = 0; i < x.length; i += 16) + { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + + a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936); + d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586); + c = this.md5_ff(c, d, a, b, x[i+ 2], 17, 606105819); + b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330); + a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897); + d = this.md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426); + c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341); + b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983); + a = this.md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416); + d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417); + c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063); + b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162); + a = this.md5_ff(a, b, c, d, x[i+12], 7 , 1804603682); + d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101); + c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290); + b = this.md5_ff(b, c, d, a, x[i+15], 22, 1236535329); + + a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510); + d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632); + c = this.md5_gg(c, d, a, b, x[i+11], 14, 643717713); + b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302); + a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691); + d = this.md5_gg(d, a, b, c, x[i+10], 9 , 38016083); + c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335); + b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848); + a = this.md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438); + d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690); + c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961); + b = this.md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501); + a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467); + d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784); + c = this.md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473); + b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734); + + a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558); + d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463); + c = this.md5_hh(c, d, a, b, x[i+11], 16, 1839030562); + b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556); + a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060); + d = this.md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353); + c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632); + b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640); + a = this.md5_hh(a, b, c, d, x[i+13], 4 , 681279174); + d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222); + c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979); + b = this.md5_hh(b, c, d, a, x[i+ 6], 23, 76029189); + a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487); + d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835); + c = this.md5_hh(c, d, a, b, x[i+15], 16, 530742520); + b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651); + + a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844); + d = this.md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415); + c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905); + b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055); + a = this.md5_ii(a, b, c, d, x[i+12], 6 , 1700485571); + d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606); + c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523); + b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799); + a = this.md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359); + d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744); + c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380); + b = this.md5_ii(b, c, d, a, x[i+13], 21, 1309151649); + a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070); + d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379); + c = this.md5_ii(c, d, a, b, x[i+ 2], 15, 718787259); + b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551); + + a = this.safe_add(a, olda); + b = this.safe_add(b, oldb); + c = this.safe_add(c, oldc); + d = this.safe_add(d, oldd); + } + return Array(a, b, c, d); +} + +MessageDigestAlgorithm.prototype.md5_cmn=function(q, a, b, x, s, t) +{ + return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q), this.safe_add(x, t)), s),b); +} + +MessageDigestAlgorithm.prototype.md5_ff=function(a, b, c, d, x, s, t) +{ + return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t); +} + +MessageDigestAlgorithm.prototype.md5_gg=function(a, b, c, d, x, s, t) +{ + return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t); +} + +MessageDigestAlgorithm.prototype.md5_hh=function(a, b, c, d, x, s, t) +{ + return this.md5_cmn(b ^ c ^ d, a, b, x, s, t); +} + +MessageDigestAlgorithm.prototype.md5_ii=function(a, b, c, d, x, s, t) +{ + return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t); +} + +MessageDigestAlgorithm.prototype.safe_add=function(x, y) +{ + var lsw = (x & 0xFFFF) + (y & 0xFFFF); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); +} +MessageDigestAlgorithm.prototype.bit_rol=function(num, cnt) +{ + return (num << cnt) | (num >>> (32 - cnt)); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Separators class. + * @see gov/nist/core/Separators.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Separators() { + this.classname="Separators"; +} + +Separators.prototype.SEMICOLON = ";"; +Separators.prototype.COLON = ":"; +Separators.prototype.COMMA = ","; +Separators.prototype.SLASH = "/"; +Separators.prototype.SP = " "; +Separators.prototype.EQUALS = "="; +Separators.prototype.STAR = "*"; +Separators.prototype.NEWLINE = "\r\n"; +Separators.prototype.RETURN = "\n"; +Separators.prototype.LESS_THAN = "<"; +Separators.prototype.GREATER_THAN = ">"; +Separators.prototype.AT = "@"; +Separators.prototype.DOT = "."; +Separators.prototype.QUESTION = "?"; +Separators.prototype.POUND = "#"; +Separators.prototype.AND = "&"; +Separators.prototype.LPAREN = "("; +Separators.prototype.RPAREN = ")"; +Separators.prototype.DOUBLE_QUOTE = "\""; +Separators.prototype.QUOTE = "\'"; +Separators.prototype.HT = "\t"; +Separators.prototype.PERCENT = "%";/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/** + * SdpException + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + + +/** + * constructor + * + * @param message exception message + */ +function SdpException(message) { + this.classname="SdpException"; + this.message=""; + if(arguments.length==1) + { + if (typeof arguments[0] == 'string') { + this.message+=arguments[0]; + } + } +} + +SdpException.prototype.constructor=SdpException; + +SdpException.prototype.getMessage =function(){ + return this.message; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SDPObject . + * @see gov/nist/javax/sdp/fields/SDPObject.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function SDPObject() { + this.classname="SDPObject"; + +} + +SDPObject.prototype = new GenericObject(); +SDPObject.prototype.constructor=SDPObject; + +SDPObject.prototype.encode =function() { + throw new SdpException("SDPObject:encode() not implemented"); +} + +SDPObject.prototype.toString =function() { + return this.encode(); +} + +SDPObject.prototype.equals =function(that) { + throw new SdpException("SDPObject:equals() not implemented"); +} + +SDPObject.prototype.match =function(other) { + throw new SdpException("SDPObject:other() not implemented"); +} + +SDPObject.prototype.clone =function(other) { + var objClone; + if (this.constructor == Object){ + objClone = new this.constructor(); + }else{ + objClone = new this.constructor(this.valueOf()); + } + for(var key in this){ + if ( objClone[key] != this[key] ){ + if ( typeof(this[key]) == 'object' ){ + objClone[key] = this[key].clone(); + }else{ + objClone[key] = this[key]; + } + } + } + objClone.toString = this.toString; + objClone.valueOf = this.valueOf; + return objClone; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SDPField . + * @see gov/nist/javax/sdp/fields/SDPField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function SDPField() { + this.classname="SDPField"; + this.fieldName=null; +} + +SDPField.prototype = new SDPObject(); +SDPField.prototype.constructor=SDPField; + +SDPField.prototype.SESSION_NAME_FIELD = "s="; +SDPField.prototype.INFORMATION_FIELD = "i="; +SDPField.prototype.EMAIL_FIELD = "e="; +SDPField.prototype.PHONE_FIELD = "p="; +SDPField.prototype.CONNECTION_FIELD = "c="; +SDPField.prototype.BANDWIDTH_FIELD = "b="; +SDPField.prototype.ORIGIN_FIELD = "o="; +SDPField.prototype.TIME_FIELD = "t="; +SDPField.prototype.KEY_FIELD = "k="; +SDPField.prototype.ATTRIBUTE_FIELD = "a="; +SDPField.prototype.VERSION_FIELD="v="; +SDPField.prototype.URI_FIELD = "u="; +SDPField.prototype.MEDIA_FIELD = "m="; +SDPField.prototype.REPEAT_FIELD = "r="; +SDPField.prototype.ZONE_FIELD = "z="; + +SDPField.prototype.BASE64="base64"; +SDPField.prototype.PROMPT="prompt"; +SDPField.prototype.CLEAR = "clear"; +SDPField.prototype.URI="URI"; +SDPField.prototype.IPV4="IP4"; +SDPField.prototype.IPV6="IP6"; +SDPField.prototype.IN="IN"; + +SDPField.prototype.getFieldName =function() { + return this.fieldName; +} + +SDPField.prototype.setFieldName =function(fieldName) { + this.fieldName=fieldName; +} + +/** Returns the type character for the field. + * @return the type character for the field. + */ +SDPField.prototype.getTypeChar =function() { + if (this.fieldName == null) + return ''; + else + return this.fieldName.charAt(0); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AttributeField . + * @see gov/nist/javax/sdp/fields/AttributeField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function AttributeField() { + this.classname="AttributeField"; + this.fieldName=this.ATTRIBUTE_FIELD; + this.attribute=null; +} + +AttributeField.prototype = new SDPField(); +AttributeField.prototype.constructor=AttributeField; + +/** Returns the name of this attribute + * @throws SdpParseException if the name is not well formatted. + * @return a String identity or null. + */ +AttributeField.prototype.getName =function() { + if (this.attribute == null) return null; + else return this.attribute.getName(); +} + +/** Sets the id of this attribute. + * @param name the string name/id of the attribute. + * @throws SdpException if the name is null + */ +AttributeField.prototype.setName =function(name) { + if(typeof(name)=='string') + { + if (this.attribute == null) this.attribute = new NameValue(); + this.attribute.setSeparator(Separators.prototype.COLON); + this.attribute.setName(name); + } + else throw new SdpException("AttributeField.setName() requires string object argument"); +} + + +/** Determines if this attribute has an associated value. + * @throws SdpParseException if the value is not well formatted. + * @return true if the attribute has a value. + */ +AttributeField.prototype.hasValue =function() { + if (this.attribute == null) + return false; + else { + var value = this.attribute.getValueAsObject(); + if (value == null) + return false; + else + return true; + } +} + +/** Returns the value of this attribute. + * @throws SdpParseException if the value is not well formatted. + * @return the value; null if the attribute has no associated value. + */ +AttributeField.prototype.getValue =function() { + if (this.attribute == null) + return null; + else { + var value = this.attribute.getValueAsObject(); + if (value == null) + return null; + else if (value instanceof String) + return value; + else + return value.toString(); + } +} + +/** Sets the value of this attribute. + * @param value the - attribute value + * @throws SdpException if the value is null. + */ +AttributeField.prototype.setValue =function(value) { + if (this.attribute == null) + this.attribute = new NameValue(); + this.attribute.setValueAsObject(value); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +AttributeField.prototype.encode =function() { + if(this.attribute ==null) throw new SdpException("AttributeField.encode() requires name/value"); + var encoded_string = this.ATTRIBUTE_FIELD; + encoded_string += this.attribute.encode(); + encoded_string += Separators.prototype.NEWLINE; + return encoded_string; +} + +AttributeField.prototype.equals =function(that) { + if ( ! (that instanceof AttributeField)) return false; + var other = that; + return (other.attribute.getName().toLowerCase()==this.attribute.getName().toLowerCase()) && + this.attribute.getValueAsObject().equals(other.attribute.getValueAsObject()); +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP VersionField . + * @see gov/nist/javax/sdp/fields/ProtoVersionField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function VersionField() { + this.classname="VersionField"; + this.fieldName=this.VERSION_FIELD; + this.version=0; +} + +VersionField.prototype = new SDPField(); +VersionField.prototype.constructor=VersionField; + +VersionField.prototype.getVersion =function() { + return this.version; +} + +/** + * Set the protoVersion member + */ +VersionField.prototype.setVersion =function(version) { + if(typeof version == 'number') + { + if(version >= 0) this.version = version; + else throw new SdpException("VersionField.setVersion(): bad argument"); + } + else throw new SdpException("VersionField.setVersion(): requires number type argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +VersionField.prototype.encode =function() { + return this.VERSION_FIELD + this.version + Separators.prototype.NEWLINE; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP OriginField . + * @see gov/nist/javax/sdp/fields/OriginField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function OriginField() { + this.classname="OriginField"; + this.fieldName=this.ORIGIN_FIELD; + this.userName=null; + this.networkType=SDPField.prototype.IN; + this.addressType=SDPField.prototype.IPV4; + this.host=null; + this.sessionId=null; + this.sessionVersion=null; +} + +OriginField.prototype = new SDPField(); +OriginField.prototype.constructor=OriginField; + + +/** Returns the name of the session originator. + * @throws SdpParseException + * @return the string username. + */ +OriginField.prototype.getUserName =function() { + return this.userName; +} + +/** + * Get the sessionID member. + */ +OriginField.prototype.getSessionId =function() { + return new Number(this.sessionId); +} + +/** + * Get the sessionVersion member. + */ +OriginField.prototype.getSessionVersion =function() { + return new Number(this.sessionVersion); +} + +/** + * Get the netType member. + */ +OriginField.prototype.getNetworkType =function() { + return this.networkType; +} + +/** + * Get the address type member. + */ +OriginField.prototype.getAddressType =function() { + return this.addressType; +} + +/** + * Get the host member. + */ +OriginField.prototype.getHost =function() { + return this.host; +} + +/** Returns the type of the network for this Connection. + * @throws SdpParseException + * @return the string network type. + */ +OriginField.prototype.getAddress =function() { + var host = this.getHost(); + if (host == null) + return null; + else + return host.getAddress(); +} + +/** + * Set the sessId member + */ +OriginField.prototype.setSessionId =function(sessionId) { + if(typeof(sessionId)=='string' || typeof(sessionId)=='number') + { + this.sessionId=""; + this.sessionId+=sessionId; + } + else throw new SdpException("OriginField.setSessionId() requires string or number object argument"); +} + +/** + * Set the sessVersion member + */ +OriginField.prototype.setSessionVersion =function(sessionVersion) { + if(typeof(sessionVersion)=='string' || typeof(sessionVersion)=='number') + { + this.sessionVersion=""; + this.sessionVersion+=sessionVersion; + } + else throw new SdpException("OriginField.setSessionVersion() requires string or number argument"); + +} + +/** + * Set the nettype member + */ +OriginField.prototype.setNetworkType =function(networkType) { + if(typeof(networkType)=='string') + { + this.networkType = networkType; + } + else throw new SdpException("OriginField.setNetworkType() requires string argument"); + +} + +/** + * Set the addrtype member + */ +OriginField.prototype.setAddressType =function(addressType) { + if(typeof(addressType)=='string') + { + this.addressType = addressType; + } + else throw new SdpException("OriginField.setAddressType() requires string argument"); +} + + +/** + * Set the address member + */ +OriginField.prototype.setHost =function(host) { + if (host instanceof Host) { + this.host = host; + } + else throw new SdpException("OriginField.setHost() requires Host object argument"); +} + + +/** + * Set the address member + */ +OriginField.prototype.setAddress =function(address) { + if (typeof(address) =='string' ) { + var host = this.getHost(); + if (host == null) host = new Host(); + host.setAddress(address); + this.setHost(host); + } + else throw new SdpException("OriginField.setAddress() requires string argument"); +} + + +/** Sets the name of the session originator. + * @param user the string username. + * @throws SdpException if the parameter is null + */ +OriginField.prototype.setUserName =function(userName) { + if(typeof(userName)=='string') + { + this.userName = userName; + } + else throw new SdpException("OriginField.setUserName() requires string argument"); +} + + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +OriginField.prototype.encode =function() { + if(this.host == null) throw new SdpException("OriginField.encode() requires host"); + if(this.userName == null) throw new SdpException("OriginField.encode() requires userName"); + if(this.sessionId == null) throw new SdpException("OriginField.encode() requires sessionId"); + if(this.sessionVersion == null) throw new SdpException("OriginField.encode() requires sessionVersion"); + + var hostEncoding = ""; + if (this.host != null){ + hostEncoding = this.host.encode(); + //it appears that SDP does not allow square brackets + //in the connection address (see RFC4566) so make sure + //we lose them + if(Host.prototype.isIPv6Address(hostEncoding)) + { + //the isIPv6Reference == true means we have a minimum + //of 2 symbols, so substring bravely + hostEncoding = hostEncoding.substring(1, hostEncoding.length()-1); + } + } + var encodedString = this.ORIGIN_FIELD; + encodedString+= this.userName + encodedString+= Separators.prototype.SP + encodedString+= this.sessionId + encodedString+= Separators.prototype.SP + encodedString+= this.sessionVersion + encodedString+= Separators.prototype.SP + encodedString+= this.networkType + encodedString+= Separators.prototype.SP + encodedString+= this.addressType + encodedString+= Separators.prototype.SP + encodedString+= hostEncoding + encodedString+= Separators.prototype.NEWLINE; + return encodedString; +} + +OriginField.prototype.clone =function() { + var retval = new OriginField(); + retval.userName = this.userName; + retval.networkType = this.networkType; + retval.addressType = this.addressType; + retval.sessionId = this.sessionId; + retval.sessionVersion = this.sessionVersion; + retval.host = new Host(this.host.getAddress()); + return retval; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SessionNameField . + * @see gov/nist/javax/sdp/fields/SessionNameField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function SessionNameField() { + this.classname="SessionNameField"; + this.fieldName=this.SESSION_NAME_FIELD; + this.sessionName=null; +} + +SessionNameField.prototype = new SDPField(); +SessionNameField.prototype.constructor=SessionNameField; + + +SessionNameField.prototype.getSessionName =function() { + return this.sessionName; +} + +/** + * Set the sessionName member + */ +SessionNameField.prototype.setSessionName =function(sessionName) { + if( typeof(sessionName)=='string') + { + this.sessionName = sessionName; + } + else throw new SdpException("SessionNameField.setSessionName() requires string object argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +SessionNameField.prototype.encode =function() { + return this.SESSION_NAME_FIELD + this.sessionName + Separators.prototype.NEWLINE; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP InformationField . + * @see gov/nist/javax/sdp/fields/InformationField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function InformationField() { + this.classname="InformationField"; + this.fieldName=this.INFORMATION_FIELD; + this.information=""; +} + +InformationField.prototype = new SDPField(); +InformationField.prototype.constructor=InformationField; + + +InformationField.prototype.getInformation =function() { + return this.information; +} + +InformationField.prototype.setInformation =function(info) { + if(typeof(info) == 'string') + { + this.information = info; + } + else throw new SdpException("InformationField.setInformation() requires string type argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +InformationField.prototype.encode =function() { + if(this.information == null) throw new SdpException("InformationField.encode() requires information"); + return this.INFORMATION_FIELD + this.information + Separators.prototype.NEWLINE; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP URIField . + * @see gov/nist/javax/sdp/fields/URIField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function URIField() { + this.classname="URIField"; + this.fieldName=this.URI_FIELD; + this.uri=""; +} + +URIField.prototype = new SDPField(); +URIField.prototype.constructor=URIField; + +URIField.prototype.getURI =function() { + return this.uri; +} + +URIField.prototype.setURI =function(uri) { + if(typeof uri == "string") + { + this.uri = uri; + } + else throw new SdpException("URIField.setURI() requires string object argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +URIField.prototype.encode =function() { + if(this.uri == null) throw new SdpException("URIField.encode() requires uri"); + return this.URI_FIELD + this.uri + Separators.prototype.NEWLINE; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP EmailField . + * @see gov/nist/javax/sdp/fields/EmailField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function EmailField() { + this.classname="EmailField"; + this.fieldName=this.EMAIL_FIELD; + this.displayName=null; + this.email=null; +} + +EmailField.prototype = new SDPField(); +EmailField.prototype.constructor=EmailField; + +EmailField.prototype.getEmail =function() { + return this.email; +} + +EmailField.prototype.getDisplayName =function() { + return this.displayName; +} + +/** + * Set the displayName member + */ +EmailField.prototype.setDisplayName =function(displayName) { + if( typeof(displayName)=='string') + { + this.displayName = displayName; + } + else throw new SdpException("EmailField.setDisplayName() requires string object argument"); +} + +/** + * Set the email member + */ +EmailField.prototype.setEmail =function(email) { + if(typeof(email)=='string') + { + this.email = email; + } + else throw new SdpException("EmailField.setEmail() requires string object argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +EmailField.prototype.encode =function() { + if(this.email==null) throw new SdpException("PhoneField.encode() requires email"); + var encodedString=this.EMAIL_FIELD; + if (this.displayName != null) encodedString = this.displayName + Separators.prototype.LESS_THAN; + else encodedString = ""; + encodedString += this.email; + if (this.displayName != null) encodedString += Separators.prototype.GREATER_THAN; + encodedString += Separators.prototype.NEWLINE; + return encodedString +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP PhoneField . + * @see gov/nist/javax/sdp/fields/PhoneField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function PhoneField() { + this.classname="PhoneField"; + this.fieldName=this.VERSION_FIELD; + this.name=null; + this.phoneNumber=null; +} + +PhoneField.prototype = new SDPField(); +PhoneField.prototype.constructor=PhoneField; + +OriginField.prototype.getName =function() { + return this.name; +} + +PhoneField.prototype.getPhoneNumber =function() { + return this.phoneNumber; +} + +/** + * Set the name member + * + *@param name - the name to set. + */ +PhoneField.prototype.setName =function(name) { + if(typeof name == 'string') + { + this.name = name; + } + else throw new SdpException("PhoneField.setName(): requires number type argument"); +} + +/** + * Set the phoneNumber member + *@param phoneNumber - phone number to set. + */ +PhoneField.prototype.setPhoneNumber =function(phoneNumber) { + if(typeof phoneNumber == 'string') + { + this.phoneNumber = phoneNumber; + } + else throw new SdpException("PhoneField.setPhoneNumber(): requires number type argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + * Here, we implement only the "name " form + * and not the "phoneNumber (name)" form + */ +PhoneField.prototype.encode =function() { + if(this.phoneNumber==null) throw new SdpException("PhoneField.encode() requires phoneNumber"); + var encodedString=this.PHONE_FIELD; + if(this.name!=null) encodedString += this.name; + encodedString += Separators.prototype.LESS_THAN; + encodedString += this.phoneNumber; + encodedString += Separators.prototype.GREATER_THAN; + encodedString += Separators.prototype.NEWLINE; + return encodedString; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SessionNameField . + * @see gov/nist/javax/sdp/fields/SessionNameField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function TimeField() { + this.classname="TimeField"; + this.fieldName=this.TIME_FIELD; + this.startTime=0; + this.stopTime=0; +} + +TimeField.prototype = new SDPField(); +TimeField.prototype.constructor=TimeField; + +TimeField.prototype.getStartTime =function() { + return this.startTime; +} + +TimeField.prototype.getStopTime =function() { + return this.stopTime; +} + +/** + * Set the startTime member + */ +TimeField.prototype.setStartTime =function(startTime) { + if(typeof(startTime) == 'number') + { + this.startTime=startTime; + } + else throw new SdpException("TimeField.setStartTime() requires string type argument"); +} + +/** + * Set the stopTime member + */ +TimeField.prototype.setStopTime =function(stopTime) { + if(typeof(stopTime) == 'number') + { + this.startTime=stopTime; + } + else throw new SdpException("TimeField.setStopTime() requires string type argument"); +} + + +/** Returns whether the field will be output as a typed time + * or a integer value. + * + * Typed time is formatted as an integer followed by a unit character. + * The unit indicates an appropriate multiplier for + * the integer. + * + * The following unit types are allowed. + * d - days (86400 seconds) + * h - hours (3600 seconds) + * m - minutes (60 seconds) + * s - seconds ( 1 seconds) + * @return true, if the field will be output as a + * typed time; false, if as an integer value. + */ +TimeField.prototype.getTypedTime =function() { + return false; +} + +/** Sets whether the field will be output as a typed time or a integer value. + * + * Typed time is formatted as an integer followed by a unit character. + * The unit indicates an appropriate multiplier for + * the integer. + * + * The following unit types are allowed. + * d - days (86400 seconds) + * h - hours (3600 seconds) + * m - minutes (60 seconds) + * s - seconds ( 1 seconds) + * @param typedTime typedTime - if set true, the start and stop times will + * be output in an optimal typed time format; if false, the + * times will be output as integers. + */ +TimeField.prototype.setTypedTime =function(typedTime) { +} + + +/** Returns whether the start and stop times were set to zero (in NTP). + * @return boolean + */ +TimeField.prototype.isZero =function() { + return (this.getStartTime()==0 && this.getStopTime()==0); +} + +/** Sets the start and stop times to zero (in NTP). + */ +TimeField.prototype.setZero =function() { + this.setStopTime(0); + this.setStartTime(0); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +TimeField.prototype.encode =function() { + var encodedString=this.TIME_FIELD; + encodedString+=this.startTime; + encodedString+=Separators.prototype.SP; + encodedString+=this.stopTime; + encodedString+=Separators.prototype.NEWLINE; + return encodedString; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ConnectionAddress . + * @see gov/nist/javax/sdp/fields/ConnectionAddress.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function ConnectionAddress() { + this.classname="ConnectionAddress"; + this.host =null; + this.ttl=0; + this.port=0; +} + +ConnectionAddress.prototype = new SDPObject(); +ConnectionAddress.prototype.constructor=ConnectionAddress; + + +ConnectionAddress.prototype.getHost =function() { + return this.host; +} + +ConnectionAddress.prototype.getTtl =function() { + return this.ttl; +} + +ConnectionAddress.prototype.getPort =function() { + return this.port; +} + +/** + * Set the address member + */ +ConnectionAddress.prototype.setHost =function(host) { + if(host instanceof Host) + { + this.host = host; + } + else throw new SdpException("ConnectionAddress.setHost() requires Host object argument"); +} + +/** + * Set the ttl member + */ +ConnectionAddress.prototype.setTtl =function(ttl) { + if(typeof(ttl)=='number') + { + this.ttl = ttl; + } + else throw new SdpException("ConnectionAddress.setTtl() requires number object argument"); +} + + +/** + * Set the port member + */ +ConnectionAddress.prototype.setPort =function(port) { + if(typeof(port)=='number') + { + this.port = port; + } + else throw new SdpException("ConnectionAddress.setPort() requires number object argument"); + +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +ConnectionAddress.prototype.encode =function() { + if(this.host==null) throw new SdpException("ConnectionAddress.encode() requires host"); + var encodedString = this.host.encode(); + //it appears that SDP does not allow square brackets + //in the connection address (see RFC4566) so make sure + //we lose them + if(Host.prototype.isIPv6Address(encodedString)) + { + //the isIPv6Reference == true means we have a minimum + //of 2 symbols, so substring bravely + encodedString += encodedString.substring(1, encodedString.length()-1); + } + + if (this.ttl != 0 && this.port != 0) { + encodedString += Separators.prototype.SLASH + this.ttl + Separators.prototype.SLASH + this.port; + } else if (this.ttl != 0) { + encodedString += Separators.prototype.SLASH + this.ttl; + } + return encodedString; +} + + +ConnectionAddress.prototype.clone =function() { + var retval = new ConnectionAddress(); + if (this.address != null) + retval.address = this.address.clone(); + retval.ttl = this.ttl; + retval.port = this.port; + return retval; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ConnectionField . + * @see gov/nist/javax/sdp/fields/ConnectionField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function ConnectionField() { + this.classname="ConnectionField"; + this.fieldName=this.CONNECTION_FIELD; + this.networkType=SDPField.prototype.IN; + this.addressType=SDPField.prototype.IPV4; + this.address=null; +} + +ConnectionField.prototype = new SDPField(); +ConnectionField.prototype.constructor=ConnectionField; + + +ConnectionField.prototype.getNetworkType =function() { + return this.networkType; +} + +ConnectionField.prototype.getAddressType =function() { + return this.addressType; +} + +ConnectionField.prototype.getAddress =function() { + if (this.address == null) + return null; + else { + var host = this.address.getAddress(); + if (host == null) + return null; + else + return host.getAddress(); + } +} + +/** + * Set the nettype member + */ +ConnectionField.prototype.setNetworkType =function(networkType) { + if(typeof networkType == 'string') + { + this.networkType = networkType; + } + else throw new SdpException("ConnectionField.setNetworkType() requires string type argument"); +} + +/** + * Set the addrtype member + */ +ConnectionField.prototype.setAddressType =function(addressType) { + if(typeof addressType == 'string') + { + this.addressType = addressType; + } + else throw new SdpException("ConnectionField.setAddressType() requires string type argument"); +} + + +/** + * Set the address member + */ +ConnectionField.prototype.setAddress =function(address) { + if(address instanceof ConnectionAddress) + { + this.address = address; + } + else if(typeof address == 'string') + { + if (this.address == null) { + this.address = new ConnectionAddress(); + var host = new Host(address); + this.address.setHost(host); + } else { + var host = this.address.getHost(); + if (host == null) { + host = new Host(address); + this.address.setAddress(host); + } else + host.setAddress(address); + } + } + else throw new SdpException("ConnectionField.setAddress() requires ConnectionAddress object or string argument"); +} + + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +ConnectionField.prototype.encode =function() { + if(this.address == null) throw new SdpException("ConnectionField.encode() requires address"); + var encoded_string = this.CONNECTION_FIELD; + encoded_string += this.networkType; + encoded_string += Separators.prototype.SP; + encoded_string += this.addressType; + encoded_string += Separators.prototype.SP; + encoded_string += this.address.encode(); + encoded_string += Separators.prototype.NEWLINE; + return encoded_string; +} + + +ConnectionField.prototype.clone =function() { + var retval = new ConnectionField(); + if (this.address != null) + retval.address = this.address.clone(); + retval.networkType=this.networkType; + retval.addressType=this.addressType; + return retval; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP KeyField . + * @see gov/nist/javax/sdp/fields/KeyField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function KeyField() { + this.classname="KeyField"; + this.fieldName=this.KEY_FIELD; + this.method=null; + this.key=null; + +} + +KeyField.prototype = new SDPField(); +KeyField.prototype.constructor=KeyField; + +KeyField.prototype.getMethod =function() { + return this.method; +} + +KeyField.prototype.getKey =function() { + return this.key; +} + +/** + * Set the type member + */ +KeyField.prototype.setMethod =function(method) { + if(typeof method == "string") + { + this.method = method; + } + else throw new SdpException("KeyField.setMethod() requires string object argument"); +} + +/** + * Set the keyData member + */ +KeyField.prototype.setKey =function(key) { + if(typeof key == "string") + { + this.key = key; + } + else throw new SdpException("KeyField.setKey() requires string object argument"); +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +KeyField.prototype.encode =function() { + if(this.method==null) throw new SdpException("KeyField.encode() requires method"); + if(this.key==null) throw new SdpException("KeyField.encode() requires key"); + var encodedString=this.KEY_FIELD; + encodedString += this.method; + encodedString += Separators.prototype.COLON; + encodedString += this.key; + encodedString += Separators.prototype.NEWLINE; + return encodedString; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP MediaField . + * @see gov/nist/javax/sdp/fields/MediaField.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function MediaField() { + this.classname="MediaField"; + this.fieldName=this.MEDIA_FIELD; + this.type=null; + this.port=-1; + this.nports=0; + this.protocol=null; + this.formatArray=new Array(); +} + +MediaField.prototype = new SDPField(); +MediaField.prototype.constructor=MediaField; + +/** Returns the type (audio,video etc) of the + * media defined by this description. + * @throws SdpParseException + * @return the string media type. + */ +MediaField.prototype.getType =function() { + return this.type; +} + +/** Returns the protocol over which this media should be transmitted. + * @throws SdpParseException + * @return the String protocol, e.g. RTP/AVP. + */ +MediaField.prototype.getPort =function() { + return this.port; +} + + +/** Returns the number of ports associated with this media description + * @throws SdpParseException + * @return the integer port count. + */ +MediaField.prototype.getNports =function() { + return this.nports; +} + +MediaField.prototype.getProtocol =function() { + return this.protocol; +} + +MediaField.prototype.getFormats =function() { + return this.formatArray; +} + +/** + * Set the media member + */ +MediaField.prototype.setType =function(type) { + if(typeof(type)=='string') + { + this.type=type.toLowerCase(); + } + else throw new SdpException("MediaField.setMedia() requires string object argument"); +} + + +/** + * Set the port member + */ +MediaField.prototype.setPort =function(port) { + if(typeof(port)=='number') + { + if(port<0) throw new SdpException("MediaField.setPort() requires number > 0 object argument"); + this.port=port; + } + else throw new SdpException("MediaField.setPort() requires number object argument"); +} + +/** + * Set the nports member + */ +MediaField.prototype.setNports =function(nports) { + if(typeof(nports)=='number') + { + if(nports<0) throw new SdpException("MediaField.setNports() requires number > 0 object argument"); + this.nports=nports; + } + else throw new SdpException("MediaField.setNports() requires number object argument"); +} + +/** + * Set the proto member + */ +MediaField.prototype.setProtocol =function(protocol) { + if(typeof(protocol)=='string') + { + this.protocol=protocol; + } + else throw new SdpException("MediaField.setProtocol() requires string object argument"); +} + +/** + * Set the fmt member + */ +MediaField.prototype.setFormats =function(formatArray) { + if(formatArray instanceof Array) + { + this.formatArray=formatArray; + } + else throw new SdpException("MediaField.setFormats() requires Array object argument"); +} + + +/** Returns an Vector of the media formats supported by this description. + * Each element in this Vector will be an String value which matches one of + * the a=rtpmap: attribute fields of the media description. + * @param create to set + * @throws SdpException + * @return the Vector. + */ +MediaField.prototype.getFormats =function(create) { + if(typeof(create)=='boolean') + { + if (create && this.formatArray==null) this.formatArray = new Array(); + return this.formatArray; + } + else throw new SdpException("MediaField.getFormats() requires boolean object argument"); +} + + +MediaField.prototype.encodeFormats =function() { + var encodedString = ""; + for (var i = 0; i < this.formatArray.length; i++) { + encodedString+=this.formatArray[i]; + if (i < (this.formatArray.length - 1)) + encodedString+=Separators.prototype.SP; + } + return encodedString; +} + +/** + * Get the string encoded version of this object + * @since v1.0 + */ +MediaField.prototype.encode =function() { + if(this.type==null) throw new SdpException("MediaField.encode() requires type"); + if(this.protocol==null) throw new SdpException("MediaField.encode() requires protocol"); + if(this.formatArray==null) throw new SdpException("MediaField.encode() requires format"); + var encodedString=this.MEDIA_FIELD; + encodedString += this.type + Separators.prototype.SP + this.port; + // Workaround for Microsoft Messenger contributed by Emil Ivov + // Leave out the nports parameter as this confuses the messenger. + if (this.nports > 1) + encodedString += Separators.prototype.SLASH + this.nports; + encodedString += Separators.prototype.SP + this.protocol; + encodedString += Separators.prototype.SP + this.encodeFormats(); + encodedString += Separators.prototype.NEWLINE; + return encodedString; +} + +MediaField.prototype.clone =function() { + var retval = new MediaField(); + retval.media = this.type; + retval.port = this.port; + retval.nports = this.nports; + retval.proto = this.protocol; + retval.formats=new Array(); + if (this.formatArray != null) + { + for(var i=0;i + * Set the Media Description's Precondition Fields + *

+ *

+ * issued by Miguel Freitas (IT) PTInovacao + *

+ * + * @param precondition + * Vector containing PreconditionFields + * @throws SdpException + */ +MediaDescription.prototype.setPreconditionFields=function(precondition) { + this.preconditionFields.setPreconditions(precondition); +} + +/** + *

+ * Set the Media Description's Precondition Fields + *

+ *

+ * issued by Miguel Freitas (IT) PTInovacao + *

+ * + * @param precondition + * PreconditionFields parameter + */ +MediaDescription.prototype.setPreconditions=function(precondition) { + this.preconditionFields = precondition; +} + +/** + *

+ * Get attribute fields of segmented precondition + *

+ *

+ * issued by Miguel Freitas (IT) PTInovacao + *

+ * + * @return Vector of attribute fields (segmented precondition) + */ +MediaDescription.prototype.getPreconditionFields=function() { + return this.preconditionFields.getPreconditions(); +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP TimeDescription. + * @see gov/nist/javax/sdp/TimeDescriptionImpl.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + + +/** + * constructor + * + * @param timeField (optional) time field to create this descrition from + */ +function TimeDescription() { + this.classname="TimeDescription"; + if(arguments.length==1) + { + if (arguments[0] instanceof TimeField) { + this.timeField=arguments[0]; + } else throw new SdpException("TimeDescription():TimeDescription() requires TimeField object in argument"); + + } + else this.timeField=new TimeField(); + this.repeatList=new Array(); +} + +/** + * Returns the Time field. + * + * @return Time + */ +TimeDescription.prototype.getTime =function(){ + return this.timeField; +} + +/** + * Sets the Time field. + * + * @public + * @param timeField Time to set + * @throws SdpException if the time is null + */ +TimeDescription.prototype.setTime =function(timeField) { + if(timeField instanceof TimeField) + { + this.timeField= timeField; + } + else throw new SdpException("TimeDescription.setTime() requires TimeField object argument") +} + + +/** + * Returns the list of repeat times (r= fields) specified in the + * SessionDescription. + * + * @public + * @param create boolean to set + * @return Vector + */ +TimeDescription.prototype.getRepeatTimes=function(create) { + if(create) this.repeatList=new Array(); + return this.repeatList; +} + +/** + * Returns the list of repeat times (r= fields) specified in the + * SessionDescription. + * + * @public + * @param repeatTimes Vector to set + * @throws SdpExceptionif the parameter is null + */ +TimeDescription.prototype.setRepeatTimes=function(repeatTimes) { + this.repeatList = repeatTimes; +} + +/** + * Add a repeat field. + * + * @public + * @param repeatField -- repeat field to add. + */ +TimeDescription.prototype.addRepeat=function(repeatField) { + if(repeatField instanceof RepeatField) + { + this.repeatList.push(repeatField); + } + else throw new SdpException("TimeDescription.addRepeat() requires RepeatField object argument") +} + +/** + * Retun string representation. + * @public + * @return string + */ +TimeDescription.prototype.encode=function() { + var retval = this.timeField.encode(); + for (var i = 0; i < this.repeatList.length; i++) { + var repeatField = this.repeatList[i]; + retval += repeatField.encode(); + } + return retval; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SessionDescription. + * @see gov/nist/javax/sdp/SessionDescriptionImpl.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function SessionDescription() { + this.classname="SessionDescription"; + this.currentTimeDescription=null; + this.currentMediaDescription=null; + this.versionField=null; + this.originField=null; + this.sessionNameField=null; + this.informationField=null; + this.uriField=null; + this.connectionField=null; + this.keyField=null; + this.timeFieldArray=null; + this.mediaDescriptionArray=null; + this.zoneAdjustments=null; + this.emailFieldArray=null; + this.phoneFieldArray=null; + this.bandwidthFieldArray=null; + this.attributeFieldArray=null; + if(arguments.length==1) + { + if (arguments[0] instanceof SessionDescription) { + this.copy(arguments[0]) + } else throw new SdpException("SessionDescription.SessionDescription() requires SessionDescription object arguments"); + } +} + +SessionDescription.prototype = new SDPObject(); +SessionDescription.prototype.constructor=SessionDescription; + + +/** + * Copy constructor, creates a deep copy of another SessionDescription. + * + * @param otherSessionDescription - the SessionDescription to copy from. + * @throws SdpException - if there is a problem constructing the SessionDescription. + */ +SessionDescription.prototype.copy=function(otherSessionDescription) +{ + + // If the other session description is null there's nothing to initialize + if (otherSessionDescription == null) return; + + // OK to clone the version field, no deep copy required + var otherVersion = otherSessionDescription.getVersion(); + if (otherVersion != null) { + this.setVersion(otherVersion.clone()); + } + + // OK to clone the origin field, class already does a deep copy + var otherOrigin = otherSessionDescription.getOrigin(); + if (otherOrigin != null) { + this.setOrigin(otherOrigin.clone()); + } + + // OK to clone the session name, no deep copy required + var otherSessionName = otherSessionDescription.getSessionName(); + if (otherSessionName != null) { + this.setSessionName(otherSessionName.clone()); + } + + // OK to clone the information field, just a string, no deep copy required + var otherInfo = otherSessionDescription.getInfo(); + if (otherInfo != null) { + this.setInfo(otherInfo.clone()); + } + + // URI field requires deep copy + var otherUriField = otherSessionDescription.getURI(); + if (otherUriField != null) { + var newUF = new URIField(); + newUF.setURI(otherUriField.toString()); + this.setURI(newUF); + } + + // OK to clone the connection field, class already does a deep copy + var otherConnection = otherSessionDescription.getConnection(); + if (otherConnection != null) { + this.setConnection(otherConnection.clone()); + } + + // OK to clone the key field, just a couple of strings + var otherKey = otherSessionDescription.getKey(); + if (otherKey != null) { + this.setKey(otherKey.clone()); + } + + // Deep copy each vector, starting with time descriptions + var otherTimeDescriptions = otherSessionDescription.getTimeDescriptions(false); + if (otherTimeDescriptions != null) { + var newTDs = new Array(); + for (var i = 0; i < otherTimeDescriptions.length; i++) { + var otherTimeDescription = otherTimeDescriptions[i]; + var otherTimeField = otherTimeDescription.getTime().clone(); + var newTD = new TimeDescription(otherTimeField); + var otherRepeatTimes = otherTimeDescription.getRepeatTimes(false); + if (otherRepeatTimes != null) { + for (var j = 0; j < otherRepeatTimes.length; j++) { + var otherRepeatField = otherRepeatTimes[j]; + // RepeatField clone is a deep copy + var newRF = otherRepeatField.clone(); + newTD.addRepeatField(newRF); + } + } + newTDs.push(newTD); + } + this.setTimes(newTDs); + } + + // Deep copy the email list + var otherEmails = otherSessionDescription.getEmails(false); + if (otherEmails != null) { + var newEmails = new Array(); + for (var i = 0; i < otherEmails.length; i++) + { + var otherEmailField = otherEmails[i]; + // Email field clone is a deep copy + var newEF = otherEmailField.clone(); + newEmails.push(newEF); + } + this.setEmails(newEmails); + } + + + // Deep copy the phone list + var otherPhones = otherSessionDescription.getPhones(false); + if (otherPhones != null) { + var newPhones = new Array(); + for (var i = 0; i < otherPhones.length; i++) + { + var otherPhoneField = otherPhones[i]; + // Phone field clone is a deep copy + var newPF = otherPhoneField.clone(); + newPhones.push(newPF); + } + this.setPhones(newPhones); + } + + + // Deep copy the zone adjustments list + var otherZAs = otherSessionDescription.getZoneAdjustments(false); + if (otherZAs != null) { + var newZAs = new Array(); + for (var i = 0; i < newZAs.length; i++) + { + var otherZoneField = newZAs[i]; + // Zone field clone is a deep copy + var newZF = otherZoneField.clone(); + newZAs.push(newZF); + } + this.setZoneAdjustments(newZAs); + } + + // Deep copy the bandwidth list + var otherBandwidths = otherSessionDescription.getBandwidths(false); + if (otherBandwidths != null) { + var newBandwidths = new Array(); + for (var i = 0; i < otherBandwidths.length; i++) + { + var otherBandwidthField = otherBandwidths[i]; + // Bandwidth field clone() is a shallow copy but object is not deep + var newBF = otherBandwidthField.clone(); + newBandwidths.push(newBF); + } + this.setBandwidths(newBandwidths); + } + + // Deep copy the attribute list + var otherAttributes = otherSessionDescription.getAttributes(false); + if (otherAttributes != null) { + var newAttributes = new Array(); + for (var i = 0; i < otherAttributes.length; i++) + { + var otherAttributeField = otherAttributes[i]; + // Attribute field clone() makes a deep copy but be careful: it may use reflection to copy one of its members + var newBF = otherAttributeField.clone(); + newAttributes.push(newBF); + } + this.setAttributes(newAttributes); + } + + // Deep copy the media descriptions + var otherMediaDescriptions = otherSessionDescription.getMediaDescriptions(false); + if (otherMediaDescriptions != null) { + var newMDs = new Array(); + for (var i = 0; i < otherMediaDescriptions.length; i++) + { + var otherMediaDescription = otherMediaDescriptions[i]; + var newMD = new MediaDescription(); + + // Copy the media field + var otherMediaField = otherMediaDescription.getMedia(); + if (otherMediaField != null) { + // Media field clone() makes a shallow copy, so don't use clone() + var newMF = new MediaField(); + newMF.setType(otherMediaField.getType()); + newMF.setPort(otherMediaField.getPort()); + newMF.setNports(otherMediaField.getNports()); + newMF.setProto(otherMediaField.getProto()); + var otherFormats = otherMediaField.getFormats(); + if (otherFormats != null) { + var newFormats = new Array(); + for (var j = 0; j < otherFormats.length; j++) + { + var otherFormat = otherFormats[j]; + // Convert all format objects to strings in order to avoid reflection + newFormats.push(String.valueOf(otherFormat)); + } + newMF.setFormats(newFormats); + } + newMD.setMedia(newMF); + } + + // Copy the information field (it's a shallow object, ok to clone) + var otherInfoField = otherMediaDescription.getInformation(); + if (otherInfoField != null) { + newMD.setInformation(otherInfoField.clone()); + } + + // Copy the connection field. OK to use clone(), already does a deep copy. + var otherConnectionField = otherMediaDescription.getConnection(); + if (otherConnectionField != null) { + newMD.setConnection(otherConnectionField.clone()); + } + + // Copy the bandwidth fields + var otherBFs = otherMediaDescription.getBandwidths(false); + if (otherBFs != null) { + var newBFs = new Array(); + for (var j = 0; j < otherBFs.length; j++) + { + var otherBF = otherBFs[j]; + // BandwidthField is a shallow object, ok to use clone + newBFs.push(otherBF.clone()); + } + newMD.setBandwidths(newBFs); + } + + // Copy the key field (shallow object) + var otherKeyField = otherMediaDescription.getKey(); + if (otherKeyField != null) { + newMD.setKey(otherKeyField.clone()); + } + + // Copy the attributes + var otherAFs = otherMediaDescription.getAttributeFields(); + if (otherAFs != null) { + var newAFs = new Array(); + for (var j = 0; j < otherAFs.length; j++) + { + var otherAF = otherAFs[j]; + // AttributeField clone() already makes a deep copy, but be careful. It will use reflection + // unless the attribute is a String or any other immutable object. + newAFs.push(otherAF.clone()); + } + newMD.setAttribute(newAFs); + } + newMDs.push(newMD); + } + this.setMediaDescriptions(newMDs); + } +} + + +SessionDescription.prototype.addField=function(sdpField){ + if(sdpField!=null && SDPField.prototype.isPrototypeOf(sdpField)==true) + { + if (sdpField instanceof VersionField) { + this.versionField = sdpField; + } else if (sdpField instanceof OriginField) { + this.originField = sdpField; + } else if (sdpField instanceof SessionNameField) { + this.sessionNameField = sdpField; + } else if (sdpField instanceof InformationField) { + if (this.currentMediaDescription != null) + this.currentMediaDescription.setInformation(sdpField); + else + this.informationField = sdpField; + } else if (sdpField instanceof URIField) { + this.uriField = sdpField; + } else if (sdpField instanceof ConnectionField) { + if (this.currentMediaDescription != null) + this.currentMediaDescription.setConnection(sdpField); + else + this.connectionField = sdpField; + } else if (sdpField instanceof KeyField) { + if (this.currentMediaDescription != null) + this.currentMediaDescription.setKey(sdpField); + else + this.keyField = sdpField; + } else if (sdpField instanceof EmailField) { + this.getEmails(true).push(sdpField); + } else if (sdpField instanceof PhoneField) { + this.getPhones(true).push(sdpField); + } else if (sdpField instanceof TimeField) { + this.currentTimeDescription = new TimeDescription(sdpField); + this.getTimeDescriptions(true).push(this.currentTimeDescription); + } else if (sdpField instanceof RepeatField) { + if (this.currentTimeDescription == null) { + throw new new SdpException("SessionDescription.addField(): parsing error, no time atttribut specified"); + } else { + this.currentTimeDescription.addRepeat(sdpField); + } + } else if (sdpField instanceof BandwidthField) { + if (this.currentMediaDescription != null) + this.currentMediaDescription.addBandwidth(sdpField); + else + this.getBandwidths(true).push(sdpField); + } else if (sdpField instanceof AttributeField) { + if (this.currentMediaDescription != null) { + var af = sdpField; + var s = af.getName(); + // Bug report from Andreas Bystrom + this.currentMediaDescription.addAttribute(sdpField); + } else { + this.getAttributes(true).push(sdpField); + } + + } else if (sdpField instanceof MediaField) { + this.currentMediaDescription = new MediaDescription(); + this.getMediaDescriptions(true).push(this.currentMediaDescription); + // Bug report from Andreas Bystrom + this.currentMediaDescription.setMedia(sdpField); + } + } + else throw new SdpException("SessionDescription.addField() requires SDPField object arguments"); +} + +/** + * Creates and returns a deep copy of this object + * + * @return a clone of this instance. + * @exception CloneNotSupportedException if this instance cannot be cloned. + */ +SessionDescription.prototype.clone=function() { + try { + return new SessionDescription(this); + } catch (exception) { + // throw this exception to indicate that this instance cannot be cloned + throw new CloneNotSupportedException(); + } +} + +/** + * Returns the version of SDP in use. This corresponds to the v= field of + * the SDP data. + * + * @return the integer version (-1 if not set). + */ +SessionDescription.prototype.getVersion=function() { + return this.versionField; +} + +/** + * Sets the version of SDP in use. This corresponds to the v= field of the + * SDP data. + * + * @param versionField version - the integer version. + * @throws SdpException if the version is null + */ +SessionDescription.prototype.setVersion=function(versionField) { + if(versionField instanceof VersionField) + { + this.versionField = versionField; + } else + throw new SdpException("SessionDescription.setVersion() requires VersionField object argument"); +} + +/** + * Returns information about the originator of the session. This corresponds + * to the o= field of the SDP data. + * + * @return the originator data. + */ +SessionDescription.prototype.getOrigin=function() { + return this.originField; +} + +/** + * Sets information about the originator of the session. This corresponds to + * the o= field of the SDP data. + * + * @param originField origin - the originator data. + * @throws SdpException if the origin is null + */ +SessionDescription.prototype.setOrigin=function(originField){ + if(originField instanceof OriginField) + { + this.originField = originField; + } else + throw new SdpException("SessionDescription.setOrigin() requires OriginField object argument"); +} + + +/** + * Returns the name of the session. This corresponds to the s= field of the + * SDP data. + * + * @return the session name. + */ +SessionDescription.prototype.getSessionName=function() { + return this.sessionNameField; +} + +/** + * Sets the name of the session. This corresponds to the s= field of the SDP + * data. + * + * @param sessionNameField name - the session name. + * @throws SdpException if the sessionName is null + */ +SessionDescription.prototype.setSessionName=function(sessionNameField){ + if(sessionNameField instanceof SessionNameField) + { + this.sessionNameField = sessionNameField; + } else + throw new SdpException("SessionDescription.setSessionName() requires SessionNameField object argument"); +} + +/** + * Returns value of the info field (i=) of this object. + * + * @return info + */ +SessionDescription.prototype.getInfo=function() { + return this.informationField; +} + +/** + * Sets the i= field of this object. + * + * @param informationField s - new i= value; if null removes the field + * @throws SdpException if the info is null + */ +SessionDescription.prototype.setInfo=function(informationField) { + if(informationField instanceof InformationField) + { + this.informationField = informationField; + } else + throw new SdpException("SessionDescription.setInfo() requires InformationField object argument"); +} + +/** + * Returns a uri to the location of more details about the session. This + * corresponds to the u= field of the SDP data. + * + * @return the uri. + */ +SessionDescription.prototype.getURI=function() { + return this.uriField; +} + +/** + * Sets the uri to the location of more details about the session. This + * corresponds to the u= field of the SDP data. + * + * @param uriField uri - the uri. + * @throws SdpException + * if the uri is null + */ +SessionDescription.prototype.setURI=function(uriField) { + if(uriField instanceof URIField) + { + this.uriField = uriField; + } else + throw new SdpException("SessionDescription.setURI() requires URIField object argument"); +} + + +/** + * Returns an email address to contact for further information about the + * session. This corresponds to the e= field of the SDP data. + * + * @param create + * boolean to set + * @throws SdpParseException + * @return the email address. + */ +SessionDescription.prototype.getEmails=function(create) { + if (this.emailFieldArray == null && create) { + this.emailFieldArray = new Array(); + } + return this.emailFieldArray; +} + +/** + * Sets a an email address to contact for further information about the + * session. This corresponds to the e= field of the SDP data. + * + * @param emailFieldArray email - the email address. + * @throws SdpException + * if the vector is null + */ +SessionDescription.prototype.setEmails=function(emailFieldArray){ + if(emailFieldArray instanceof Array) + { + for (var i = 0; i < emailFieldArray.length; i++) { + var emailField = emailFieldArray[i]; + if (! (emailField instanceof EmailField)) { + throw new SdpException("SessionDescription.setEmails() requires Array of EmailField object argument"); + } + } + this.emailFieldArray = emailFieldArray; + } else throw new SdpException("SessionDescription.setEmails() requires Array object argument"); +} + +/** + * Returns a phone number to contact for further information about the + * session. This corresponds to the p= field of the SDP data. + * + * @param create + * boolean to set + * @throws SdpException + * @return the phone number. + */ +SessionDescription.prototype.getPhones=function(create) { + if (this.phoneFieldArray == null) { + if (create) this.phoneFieldArray = new Array(); + } + return this.phoneFieldArray; +} + +/** + * Sets a phone number to contact for further information about the session. + * This corresponds to the p= field of the SDP data. + * + * @param phoneFieldArray phone - the phone number. + * @throws SdpException + * if the vector is null + */ +SessionDescription.prototype.setPhones=function(phoneFieldArray) { + if(phoneFieldArray instanceof Array) + { + for (var i = 0; i < phoneFieldArray.length; i++) { + var phoneField = phoneFieldArray[i]; + if (! (phoneField instanceof PhoneField)) { + throw new SdpException("SessionDescription.setPhones() requires Array of PhoneField object argument"); + } + } + this.phoneFieldArray = phoneFieldArray; + } else throw new SdpException("SessionDescription.setPhones() requires Array object argument"); +} + +/** + * Returns a TimeField indicating the start, stop, repetition and time zone + * information of the session. This corresponds to the t= field of the SDP + * data. + * + * @param create + * boolean to set + * @throws SdpException + * @return the Time Field. + */ +SessionDescription.prototype.getTimeDescriptions=function(create) { + if (this.timeFieldArray == null) { + if (create) this.timeFieldArray = new Array(); + } + return this.timeFieldArray; +} + +/** + * Sets a TimeField indicating the start, stop, repetition and time zone + * information of the session. This corresponds to the t= field of the SDP + * data. + * + * @param timeFieldArray time - the TimeField. + * @throws SdpException + * if the vector is null + */ +SessionDescription.prototype.setTimes=function(timeFieldArray) { + if(timeFieldArray instanceof Array) + { + for (var i = 0; i < timeFieldArray.length; i++) { + var timeField = timeFieldArray[i]; + if (! (timeField instanceof TimeField)) { + throw new SdpException("SessionDescription.setTimes() requires Array of TimeField object argument"); + } + } + this.timeFieldArray = timeFieldArray; + } else throw new SdpException("SessionDescription.setTimes() requires Array object argument"); +} + +/** + * Returns the time zone adjustments for the Session + * + * @param create + * boolean to set + * @throws SdpException + * @return a Hashtable containing the zone adjustments, where the key is the + * Adjusted Time Zone and the value is the offset. + */ +SessionDescription.prototype.getZoneAdjustments=function(create) { + if (this.zoneAdjustments == null) { + if (create) this.zoneAdjustments = new Array(); + } + return this.zoneAdjustments; +} + +/** + * Sets the time zone adjustment for the TimeField. + * + * @param zoneAdjustmentFieldArray + * zoneAdjustments - a Hashtable containing the zone adjustments, + * where the key is the Adjusted Time Zone and the value is the + * offset. + * @throws SdpException + * if the vector is null + */ +SessionDescription.prototype.setZoneAdjustments=function(zoneAdjustmentFieldArray) { + if(zoneAdjustmentFieldArray instanceof Array) + { + for (var i = 0; i < zoneAdjustmentFieldArray.length; i++) { + var zoneAdjustmentField = zoneAdjustmentFieldArray[i]; + if (! (zoneAdjustmentField instanceof ZoneAdjustmentField)) { + throw new SdpException("SessionDescription.setZoneAdjustments() requires Array of ZoneAdjustmentField object argument"); + } + } + this.zoneAdjustments = zoneAdjustmentFieldArray; + } + else throw new SdpException("SessionDescription.setZoneAdjustments() requires Array object argument"); +} + + +/** + * Returns the connection information associated with this object. This may + * be null for SessionDescriptions if all Media objects have a connection + * object and may be null for Media objects if the corresponding session + * connection is non-null. + * + * @return connection + */ +SessionDescription.prototype.getConnection=function() { + return this.connectionField; +} + +/** + * Set the connection data for this entity. + * + * @param connectionField to set + * @throws SdpException if the parameter is null + */ +SessionDescription.prototype.setConnection=function(connectionField){ + if(connectionField instanceof ConnectionField) + { + this.connectionField = connectionField; + } + else throw new SdpException("SessionDescription.setConnection() requires ConnectionField object argument"); +} + +/** + * Returns the Bandwidth of the specified type. + * + * @param create + * type - type of the Bandwidth to return + * @return the Bandwidth or null if undefined + */ +SessionDescription.prototype.getBandwidths=function(create) { + if (this.bandwidthFieldArray == null) { + if (create) this.bandwidthFieldArray = new Array(); + } + return this.bandwidthFieldArray; +} + +/** + * set the value of the Bandwidth with the specified type. + * + * @param bandwidthFieldArray + * to set + * @throws SdpException + * if the vector is null + */ +SessionDescription.prototype.setBandwidths=function(bandwidthFieldArray) { + if(bandwidthFieldArray instanceof Array) + { + for (var i = 0; i < bandwidthFieldArray.length; i++) { + var bandwidthField = bandwidthFieldArray[i]; + if (! (bandwidthField instanceof BandwidthField)) { + throw new SdpException("SessionDescription.setBandwidths() requires Array of BandwidthField object argument"); + } + } + this.bandwidthFieldArray = bandwidthFieldArray; + } + else throw new SdpException("SessionDescription.setBandwidths() requires Array object argument"); +} + + +/** + * Returns the integer value of the specified bandwidth name. + * + * @param name + * name - the name of the bandwidth type + * @throws SdpParseException + * @return the value of the named bandwidth + */ +SessionDescription.prototype.getBandwidth=function(name){ + if(typeof(name) == 'string') + { + if( this.bandwidthFieldArray != null) + for (var i = 0; i < this.bandwidthFieldArray.length; i++) { + if (this.bandwidthFieldArray[i].getType() == name) { + return this.bandwidthFieldArray[i].getValue(); + } + } + } else throw new SdpException("SessionDescription.getBandwidth() requires string object argument"); + return -1; +} + + +/** + * Sets the value of the specified bandwidth type. + * + * @param name name - the name of the bandwidth type. + * @param value value - the value of the named bandwidth type. + * @throws SdpException + * if the name is null + */ +SessionDescription.prototype.setBandwidth=function(name, value) { + if((typeof(name) == 'string') && (typeof(value) == 'number')) + { + if (this.bandwidthFieldArray != null) { + for (var i = 0; i < this.bandwidthFieldArray.length; i++) { + if (this.bandwidthFieldArray[i].getType() == name) { + this.bandwidthFieldArray[i].setBandwidth(value); + return; + } + } + } else this.bandwidthFieldArray= new Array(); + var bandwidthField = new BandwidthField(); + bandwidthField.setType(name); + bandwidthField.setBandwidth(value); + this.bandwidthFieldArray.push(bandwidthField); + } else throw new SdpException("SessionDescription:setBandwidth() requires string and number arguments"); +} + +/** + * Removes the specified bandwidth type. + * + * @param name: the name of the bandwidth type + */ +SessionDescription.prototype.removeBandwidth=function(name) { + if(typeof(name) == 'string') + { + if(this.bandwidthFieldArray != null) { + for (var i = 0; i < this.bandwidthFieldArray.length; i++) { + if (this.bandwidthFieldArray[i].getType() == name) { + this.bandwidthFieldArray.splice(i,1); + break; + } + } + } + } + else throw new SdpException("SessionDescription:setBandwidth() removeBandwidth require string argument"); +} + + +/** + * Returns the key data. + * + * @return key + */ +SessionDescription.prototype.getKey=function() { + return this.keyField; +} + +/** + * Sets encryption key information. This consists of a method and an + * encryption key included inline. + * + * @param keyField + * key - the encryption key data; depending on method may be null + * @throws SdpException + * if the parameter is null + */ +SessionDescription.prototype.setKey=function(keyField) { + if(keyField instanceof KeyField) + { + this.keyField = keyField; + } + else throw new SdpException("SessionDescription.setKey() requires KeyField object argument"); +} + + +/** + * Returns the value of the specified attribute. + * + * @param name + * name - the name of the attribute + * @throws SdpParseException + * @return the value of the named attribute + */ +SessionDescription.prototype.getAttribute=function(name) { + if(typeof(name)=='string') + { + if(this.attributeFieldArray == null) return null; + for (var i = 0; i < this.attributeFieldArray.length; i++) { + if (this.attributeFieldArray[i].getName()==name) { + return this.attributeFieldArray[i].getValue(); + } + } + return null; + } + else throw new SdpException("SessionDescription.getAttribute() requires string object argument"); +} + + +/** + * Returns the set of attributes for this Description as a Vector of + * Attribute objects in the order they were parsed. + * + * @param create + * create - specifies whether to return null or a new empty + * Vector in case no attributes exists for this Description + * @return attributes for this Description + */ +SessionDescription.prototype.getAttributes=function(create) { + if (this.attributeFieldArray == null) { + if (create) this.attributeFieldArray = new Array(); + } + return this.attributeFieldArray; +} + + +/** + * Removes the attribute specified by the value parameter. + * + * @param name + * name - the name of the attribute + */ +SessionDescription.prototype.removeAttribute=function(name) { + if(typeof(name)=='string') + { + if (this.attributeFieldArray != null) + { + for (var i = 0; i < this.attributeFieldArray.length; i++) { + if (this.attributeFieldArray[i].getName()==name) { + this.attributeFieldArray.slice(i,1); + break; + } + } + } + } + else throw new SdpException("SessionDescription.removeAttribute() requires string object argument"); +} + +/** + * Sets the value of the specified attribute. + * + * @param name + * name - the name of the attribute. + * @param value + * value - the value of the named attribute. + * @throws SdpException + * if the name or the value is null + */ +SessionDescription.prototype.setAttribute=function(name, value) { + if(typeof(name)=='string') + { + if(value==null) throw new SdpException("SessionDescription.setAttribute() requires not null value object argument"); + if(this.attributeFieldArray == null) this.attributeFieldArray= new Array(); + for (var i = 0; i < this.attributeFieldArray.length; i++) { + if (this.attributeFieldArray[i].getName()==name) { + { + this.attributeFieldArray[i].setValue(value); + return; + } + } + } + var newAttributeField = new AttributeField(); + newAttributeField.setName(name); + newAttributeField.setValue(value); + this.attributeFieldArray.push(newAttributeField); + } + else throw new SdpException("SessionDescription.setAttribute() requires string argument for name"); +} + +/** + * Adds the specified Attribute to this Description object. + * + * @param attributesFieldArray - the attribute to add + * @throws SdpException + * if the vector is null + */ +SessionDescription.prototype.setAttributes=function(attributeFieldArray) { + if(attributeFieldArray instanceof Array) + { + for (var i = 0; i < attributeFieldArray.length; i++) { + var attributeField = attributeFieldArray[i]; + if (! (attributeField instanceof AttributeField)) { + throw new SdpException("SessionDescription.setAttributes() requires Array of AttributeField object argument"); + } + } + this.attributeFieldArray = attributeFieldArray; + } + else throw new SdpException("SessionDescription.setAttributes() requires Array object argument"); +} + +/** + * Adds a MediaDescription to the session description. These correspond to + * the m= fields of the SDP data. + * + * @param create + * boolean to set + * @throws SdpException + * @return media - the field to add. + */ +SessionDescription.prototype.getMediaDescriptions=function(create) { + if (this.mediaDescriptionArray == null) { + if (create) this.mediaDescriptionArray = new Array(); + } + return this.mediaDescriptionArray; +} + +/** + * Removes all MediaDescriptions from the session description. + * + * @param mediaDescriptionArray + * to set + * @throws SdpException + * if the parameter is null + */ +SessionDescription.prototype.setMediaDescriptions=function(mediaDescriptionArray) { + if(mediaDescriptionArray instanceof Array) + { + for (var i = 0; i < mediaDescriptionArray.length; i++) { + var attributeField = mediaDescriptionArray[i]; + if (! (attributeField instanceof MediaDescription)) { + throw new SdpException("SessionDescription.setMediaDescriptions() requires Array of MediaDescription object argument"); + } + } + this.mediaDescriptionArray = mediaDescriptionArray; + } + else throw new SdpException("SessionDescription.setAttributes() requires Array of MediaDescription object argument"); +} + + +SessionDescription.prototype.encodeSDPFieldArray=function(array) { + var encBuff = ""; + for (var i = 0; i < array.length; i++) encBuff+=array[i].encode(); + return encBuff; +} + +/** + * Returns the canonical string representation of the current + * SessionDescrption. Acknowledgement - this code was contributed by Emil + * Ivov. + * + * @return Returns the canonical string representation of the current SessionDescrption. + */ + +SessionDescription.prototype.encode=function() { + var encodedString = ""; + encodedString+=this.versionField == null ? "" : this.versionField.encode(); + encodedString+=(this.originField == null) ? "" : this.originField.encode(); + encodedString+=this.sessionNameField == null ? "" : this.sessionNameField.encode(); + encodedString+=this.informationField == null ? "" : this.informationField.encode(); + encodedString+=this.uriField == null ? "" : this.uriField.encode(); + encodedString+=this.emailFieldArray == null ? "": this.encodeSDPFieldArray(this.emailFieldArray); + encodedString+=this.phoneFieldArray == null ? "": this.encodeSDPFieldArray(this.phoneFieldArray); + encodedString+=this.connectionField == null ? "" : this.connectionField.encode(); + encodedString+=this.bandwidthFieldArray == null ? "": this.encodeSDPFieldArray(this.bandwidthFieldArray); + encodedString+=this.timeFieldArray == null ? "": this.encodeSDPFieldArray(this.timeFieldArray); + encodedString+=this.zoneAdjustments == null ? "": this.encodeSDPFieldArray(this.zoneAdjustments); + encodedString+=this.keyField == null ? "" : this.keyField.encode(); + encodedString+=this.attributeFieldArray == null ? "": this.encodeSDPFieldArray(this.attributeFieldArray); + encodedString+=this.mediaDescriptionArray == null ? "": this.encodeSDPFieldArray(this.mediaDescriptionArray); + return encodedString; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SDPParser . + * @see gov/nist/javax/sdp/parser/SDPParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function SDPParser() { + this.classname="SDPParser"; +} + +SDPParser.prototype.constructor=SDPParser; + +SDPParser.prototype.parse =function(sdpString) { + if(typeof sdpString == 'string') + { + var sessionDescription = new SessionDescription(); + var start = 0; + var line = null; + // Return trivially if there is no sdp announce message + // to be parsed. Bruno Konik noticed this bug. + // Strip off leading and trailing junk. + var trimedSdpString = sdpString.trim() + "\r\n"; + // Bug fix by Andreas Bystrom. + while (start < trimedSdpString.length) { + // Major re-write by Ricardo Borba. + var lfPos = trimedSdpString.indexOf("\n", start); + var crPos = trimedSdpString.indexOf("\r", start); + if (lfPos > 0 && crPos < 0) { + // there are only "\n" separators + line = trimedSdpString.substring(start, lfPos); + start = lfPos + 1; + } else if (lfPos < 0 && crPos > 0) { + //bug fix: there are only "\r" separators + line = trimedSdpString.substring(start, crPos); + start = crPos + 1; + } else if (lfPos > 0 && crPos > 0) { + // there are "\r\n" or "\n\r" (if exists) separators + if (lfPos > crPos) { + // assume "\r\n" for now + line = trimedSdpString.substring(start, crPos); + // Check if the "\r" and "\n" are close together + if (lfPos == crPos + 1) { + start = lfPos + 1; // "\r\n" + } else { + start = crPos + 1; // "\r" followed by the next record and a "\n" further away + } + } else { + // assume "\n\r" for now + line = trimedSdpString.substring(start, lfPos); + // Check if the "\n" and "\r" are close together + if (crPos == lfPos + 1) { + start = crPos + 1; // "\n\r" + } else { + start = lfPos + 1; // "\n" followed by the next record and a "\r" further away + } + } + } else if (lfPos < 0 && crPos < 0) { // end + break; + } + + var sdpFieldParser = SDPParserFactory.prototype.createParser(line); + var sdpField = sdpFieldParser.parse(line); + sessionDescription.addField(sdpField); + } + return sessionDescription; + } + else throw new SdpException("SDPParser.parse() requires string object argument"); +} + + +SDPParser.prototype.parseNameValue =function(separator){ + if(separator==null) + { + var nameValue=this.parseNameValue("=") + return nameValue; + } + else + { + this.lexer.match(LexerCore.prototype.ID); + var name = this.lexer.getNextToken(); + this.lexer.SPorHT(); + try { + var quoted = false; + var la = this.lexer.lookAhead(0); + if (la == separator) { + this.lexer.consume(1); + this.lexer.SPorHT(); + var str = null; + var isFlag = false; + if (this.lexer.lookAhead(0) == '\"') { + str = this.lexer.quotedString(); + quoted = true; + } else { + this.lexer.match(LexerCore.prototype.ID); + var value = this.lexer.getNextToken(); + str = value.tokenValue; + if (str == null) { + str = ""; + isFlag = true; + } + } + var nameValue = new NameValue(name.tokenValue, str, isFlag); + if (quoted) { + nameValue.setQuotedValue(); + } + return nameValue; + } else { + nameValue=new NameValue(name.tokenValue, "", true); + return nameValue; + } + } catch(exception) { + console.error("SDPParser:parseNameValue(): catched exception:"+exception); + nameValue=new NameValue(name.tokenValue, null, false); + return nameValue; + } + } + return nameValue; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP VersionFieldParser . + * @see gov/nist/javax/sdp/parser/VersionFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function VersionFieldParser() { + this.classname="VersionFieldParser"; +} + +VersionFieldParser.prototype = new SDPParser(); +VersionFieldParser.prototype.constructor=VersionFieldParser; + +VersionFieldParser.prototype.parse =function(versionFieldString) { + if(typeof versionFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", versionFieldString); + this.lexer.match('v'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + + var versionField = new VersionField(); + this.lexer.match(LexerCore.prototype.ID); + var version = this.lexer.getNextToken(); + versionField.setVersion(parseInt(version.getTokenValue())); + this.lexer.SPorHT(); + return versionField; + } catch(exception) { + throw new SdpException("VersionFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("VersionFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SessionNameFieldParser . + * @see gov/nist/javax/sdp/parser/SessionNameFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function SessionNameFieldParser() { + this.classname="SessionNameFieldParser"; +} + +SessionNameFieldParser.prototype = new SDPParser(); +SessionNameFieldParser.prototype.constructor=SessionNameFieldParser; + +SessionNameFieldParser.prototype.parse =function(sessionNameFieldString) { + if(typeof sessionNameFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", sessionNameFieldString); + this.lexer.match('s'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var sessionNameField = new SessionNameField(); + var rest = this.lexer.getRest(); + // Some endpoints may send us a blank session name ("s=") -- [rborba] + sessionNameField.setSessionName(rest == null ? "" : rest.trim()); + return sessionNameField; + } catch(exception) { + throw new SdpException("SessionNameFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("SessionNameFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AttributeFieldParser . + * @see gov/nist/javax/sdp/parser/AttributeFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function AttributeFieldParser() { + this.classname="AttributeFieldParser"; +} + +AttributeFieldParser.prototype = new SDPParser(); +AttributeFieldParser.prototype.constructor=AttributeFieldParser; + +AttributeFieldParser.prototype.parse =function(attributeFieldString) { + if(typeof attributeFieldString == 'string') + { + this.lexer = new LexerCore("charLexer", attributeFieldString); + try { + var attributeField = new AttributeField(); + this.lexer.match('a'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var ptr = this.lexer.markInputPosition(); + try { + var name = this.lexer.getNextToken(':'); + this.lexer.consume(1); + var value = this.lexer.getRest(); + if(value!=null) value.trim(); + attributeField.setName(name); + attributeField.setValue(value) + } catch (exception) { + this.lexer.rewindInputPosition(ptr); + var name = this.lexer.getRest(); + if (name == null) throw new ParseException(this.lexer.getBuffer(),this.lexer.getPtr()); + attributeField.setName(name); + attributeField.setValue(null) + }; + this.lexer.SPorHT(); + return attributeField; + } catch(exception) { + throw new SdpException("AttributeFieldParser.parse(): parsing exception:"+this.lexer.getBuffer(), this.lexer.getPtr()); + } + } + else throw new SdpException("AttributeFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP BandwidthFieldParser . + * @see gov/nist/javax/sdp/parser/BandwidthFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function BandwidthFieldParser() { + this.classname="BandwidthFieldParser"; +} + +BandwidthFieldParser.prototype = new SDPParser(); +BandwidthFieldParser.prototype.constructor=BandwidthFieldParser; + +BandwidthFieldParser.prototype.parse =function(bandwidthFieldString) { + if(typeof bandwidthFieldString == 'string') + { + this.lexer = new LexerCore("charLexer", bandwidthFieldString); + try { + this.lexer.match('b'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var bandwidthField = new BandwidthField(); + var nameValue = this.parseNameValue(':'); + bandwidthField.setBandwidth(parseInt(nameValue.getValueAsObject())); + bandwidthField.setType(nameValue.getName()); + this.lexer.SPorHT(); + return bandwidthField; + } catch(exception) { + throw new SdpException("BandwidthFieldParser.parse(): parsing exception:"+this.lexer.getBuffer(), this.lexer.getPtr()); + } + } + else throw new SdpException("BandwidthFieldParser.parse() requires string object argument"); +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ConnectionFieldParser . + * @see gov/nist/javax/sdp/parser/ConnectionFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function ConnectionFieldParser() { + this.classname="ConnectionFieldParser"; +} + +ConnectionFieldParser.prototype = new SDPParser(); +ConnectionFieldParser.prototype.constructor=ConnectionFieldParser; + +ConnectionFieldParser.prototype.parse =function(connectionFieldString) { + if(typeof connectionFieldString == 'string') + { + this.lexer = new LexerCore("charLexer", connectionFieldString); + try { + this.lexer.match('c'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var connectionField = new ConnectionField(); + this.lexer.match(LexerCore.prototype.ID); + this.lexer.SPorHT(); + var token = this.lexer.getNextToken(); + connectionField.setNetworkType(token.getTokenValue()); + this.lexer.match(LexerCore.prototype.ID); + this.lexer.SPorHT(); + token = this.lexer.getNextToken(); + connectionField.setAddressType(token.getTokenValue()); + this.lexer.SPorHT(); + var rest = this.lexer.getRest(); + var connectionAddress = this.parseConnectionAddress(rest.trim()); + connectionField.setAddress(connectionAddress); + return connectionField; + } catch(exception) { + throw new SdpException("ConnectionFieldParser.parse(): parsing exception:"+this.lexer.getBuffer(), this.lexer.getPtr()); + } + } + else throw new SdpException("ConnectionFieldParser.parse() requires string object argument"); +} + + +ConnectionFieldParser.prototype.parseConnectionAddress =function(connectionAddressString) { + var connectionAddress = new ConnectionAddress(); + var begin = connectionAddressString.indexOf("/"); + if (begin != -1) { + connectionAddress.setAddress(new Host(connectionAddressString.substring(0, begin))); + var middle = connectionAddressString.indexOf("/", begin + 1); + if (middle != -1) { + var ttl = connectionAddressString.substring(begin + 1, middle); + connectionAddress.setTtl(parseInt(ttl.trim())); + var addressNumber = connectionAddressString.substring(middle + 1); + connectionAddress.setPort(parseInt(addressNumber.trim())); + } else { + var ttl = connectionAddressString.substring(begin + 1); + connectionAddress.setTtl(Integer.parseInt(ttl.trim())); + } + } else + connectionAddress.setHost(new Host(connectionAddressString)); + return connectionAddress; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP EmailFieldParser . + * @see gov/nist/javax/sdp/parser/EmailFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function EmailFieldParser() { + this.classname="EmailFieldParser"; +} + +EmailFieldParser.prototype = new SDPParser(); +EmailFieldParser.prototype.constructor=EmailFieldParser; + +EmailFieldParser.prototype.parse =function(emailFieldString) { + if(typeof emailFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", emailFieldString); + this.lexer.match('e'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + + var emailField = new EmailField(); + var rest = this.lexer.getRest(); + var displayName = this.parseDisplayName(rest.trim()); + if(displayName!=null) emailField.setDisplayName(displayName); + emailField.setEmail(this.parseEmail(rest)); + return emailField; + } catch(exception) { + throw new SdpException("EmailFieldParser.parse(): parsing exception:"+this.lexer.getBuffer(), this.lexer.getPtr()); + } + } + else throw new SdpException("EmailFieldParser.parse() requires string object argument"); +} + +EmailFieldParser.prototype.parseDisplayName =function(displayNameString) { + var begin = displayNameString.indexOf("("); + if(begin != -1) { + var end = displayNameString.indexOf(")"); + // e=mjh@isi.edu (Mark Handley) + return displayNameString.substring(begin + 1, end); + } else { + // The alternative RFC822 name quoting convention + // is also allowed for + // email addresses. ex: e=Mark Handley + var ind = displayNameString.indexOf("<"); + if (ind != -1) { + return displayNameString.substring(0, ind); + } else { + // There is no display name !!! + } + } + return null; +} + + +EmailFieldParser.prototype.parseEmail =function(emailString) { + var begin = emailString.indexOf("("); + if (begin != -1) { + // e=mjh@isi.edu (Mark Handley) + return emailString.substring(0, begin); + } else { + // The alternative RFC822 name quoting convention is + // also allowed for + // email addresses. ex: e=Mark Handley + var ind = emailString.indexOf("<"); + if (ind != -1) { + var end = emailString.indexOf(">"); + return emailString.substring(ind + 1, end); + } else { + var i = emailString.indexOf("\n"); + if (i != -1) { + return emailString.substring(0, i); + } else { + // Pb: the email is not well formatted + } + } + } + return null; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP InformationFieldParser . + * @see gov/nist/javax/sdp/parser/InformationFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function InformationFieldParser() { + this.classname="InformationFieldParser"; +} + +InformationFieldParser.prototype = new SDPParser(); +InformationFieldParser.prototype.constructor=InformationFieldParser; + +InformationFieldParser.prototype.parse =function(informationFieldString) { + if(typeof informationFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", informationFieldString); + this.lexer.match('i'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var informationField = new InformationField(); + informationField.setInformation(this.lexer.getRest().trim()); + return informationField; + } catch(exception) { + throw new SdpException("InformationFieldParser.parse(): parsing exception:"+this.lexer.getBuffer(), this.lexer.getPtr()); + } + } + else throw new SdpException("InformationFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP KeyFieldParser . + * @see gov/nist/javax/sdp/parser/KeyFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function KeyFieldParser() { + this.classname="KeyFieldParser"; +} + +KeyFieldParser.prototype = new SDPParser(); +KeyFieldParser.prototype.constructor=KeyFieldParser; + +KeyFieldParser.prototype.parse =function(keyFieldString) { + if(typeof keyFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", keyFieldString); + this.lexer.match('k'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var keyField = new KeyField(); + //Espen: Stealing the approach from AttributeFieldParser from from here... + var nameValue = new NameValue(); + var ptr = this.lexer.markInputPosition(); + try { + var name = this.lexer.getNextToken(':'); + this.lexer.consume(1); + var value = this.lexer.getRest(); + nameValue = new NameValue(name.trim(), value.trim()); + } catch (exception) { + this.lexer.rewindInputPosition(ptr); + var rest = this.lexer.getRest(); + if (rest == null) throw SdpException("KeyFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " +this.lexer.getPtr()); + nameValue = new NameValue(rest.trim(), null); + } + keyField.setMethod(nameValue.getName()); + keyField.setKey(nameValue.getValueAsObject()); + this.lexer.SPorHT(); + return keyField; + } catch(exception) { + throw new SdpException("KeyFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("KeyFieldParser.parse() requires string object argument"); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP OriginFieldParser . + * @see gov/nist/javax/sdp/parser/OriginFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function OriginFieldParser() { + this.classname="OriginFieldParser"; +} + +OriginFieldParser.prototype = new SDPParser(); +OriginFieldParser.prototype.constructor=OriginFieldParser; + +OriginFieldParser.prototype.parse =function(originFieldString) { + if(typeof originFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", originFieldString); + var originField = new OriginField(); + this.lexer.match('o'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var userName = this.lexer.getNextToken(' '); + originField.setUserName(userName); + this.lexer.SPorHT(); + this.lexer.match(LexerCore.prototype.ID); + //lexer.ttokenSafe(); + var sessionId = this.lexer.getNextToken(); + // guard against very long session IDs + var sessId = sessionId.getTokenValue(); + if (sessId.length > 18) sessId = sessId.substring(sessId.length - 18); + originField.setSessionId(sessId); + this.lexer.SPorHT(); + this.lexer.match(LexerCore.prototype.ID); + var sessionVersion = this.lexer.getNextToken(); + // guard against very long session Verion + var sessVer = sessionVersion.getTokenValue(); + if (sessVer.length > 18) sessVer = sessVer.substring(sessVer.length - 18); + originField.setSessionVersion(sessVer); + this.lexer.SPorHT(); + this.lexer.match(LexerCore.prototype.ID); + var networkType = this.lexer.getNextToken(); + originField.setNetworkType(networkType.getTokenValue()); + this.lexer.SPorHT(); + this.lexer.match(LexerCore.prototype.ID); + var addressType = this.lexer.getNextToken(); + originField.setAddressType(addressType.getTokenValue()); + this.lexer.SPorHT(); + var hostString = this.lexer.getRest(); + var hostNameParser = new HostNameParser(hostString); + var host = hostNameParser.host(); + originField.setHost(host); + return originField; + } catch(exception) { + throw new SdpException("OriginFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("OriginFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP PhoneFieldParser . + * @see gov/nist/javax/sdp/parser/PhoneFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function PhoneFieldParser() { + this.classname="PhoneFieldParser"; +} + +PhoneFieldParser.prototype = new SDPParser(); +PhoneFieldParser.prototype.constructor=PhoneFieldParser; + +PhoneFieldParser.prototype.parse =function(emailFieldString) { + if(typeof emailFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", emailFieldString); + this.lexer.match('p'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var phoneField = new PhoneField(); + var rest = this.lexer.getRest(); + var displayName = this.parseDisplayName(rest.trim()) + if(displayName!=null) phoneField.setName(displayName); + phoneField.setPhoneNumber(this.parsePhoneNumber(rest)); + return phoneField; + } catch(exception) { + throw new SdpException("PhoneFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("PhoneFieldParser.parse() requires string object argument"); +} + + +PhoneFieldParser.prototype.parseDisplayName =function(displayNamedString) { + var begin = displayNamedString.indexOf("("); + if (begin != -1) { + var end = displayNamedString.indexOf(")"); + // p=+44-171-380-7777 (Mark Handley) + return displayNamedString.substring(begin + 1, end); + } else { + // The alternative RFC822 name quoting convention is + // also allowed for + // email addresses. ex: p=Mark Handley <+44-171-380-7777> + var ind = displayNamedString.indexOf("<"); + if (ind != -1) { + return displayNamedString.substring(0, ind); + } else { + // There is no display name !!! + return null + } + } +} + +PhoneFieldParser.prototype.parsePhoneNumber =function(phoneNumberString) { + var begin = phoneNumberString.indexOf("("); + if (begin != -1) { + // p=+44-171-380-7777 (Mark Handley) + return phoneNumberString.substring(0, begin).trim(); + } else { + // The alternative RFC822 name quoting convention is also allowed for + // email addresses. ex: p=Mark Handley <+44-171-380-7777> + var ind = phoneNumberString.indexOf("<"); + if (ind != -1) { + var end = phoneNumberString.indexOf(">"); + return phoneNumberString.substring(ind + 1, end); + } else { + // p=+44-171-380-7777 + return phoneNumberString.trim(); + } + } +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP URIFieldParser . + * @see gov/nist/javax/sdp/parser/URIFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function URIFieldParser() { + this.classname="URIFieldParser"; +} + +URIFieldParser.prototype = new SDPParser(); +URIFieldParser.prototype.constructor=URIFieldParser; + +URIFieldParser.prototype.parse =function(uriFieldString) { + if(typeof uriFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", uriFieldString); + this.lexer.match('u'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var uriField = new URIField(); + uriField.setURI(this.lexer.getRest().trim()); + return uriField; + } catch(exception) { + throw new SdpException("URIFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("URIFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP TimeFieldParser . + * @see gov/nist/javax/sdp/parser/TimeFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function TimeFieldParser() { + this.classname="TimeFieldParser"; +} + +TimeFieldParser.prototype = new SDPParser(); +TimeFieldParser.prototype.constructor=TimeFieldParser; + +TimeFieldParser.prototype.parse =function(timeFieldString) { + if(typeof timeFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", timeFieldString); + this.lexer.match('t'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + var timeField = new TimeField(); + timeField.setStartTime(this.parseTime()); + this.lexer.SPorHT(); + timeField.setStopTime(this.parseTime()); + return timeField; + } catch(exception) { + throw new SdpException("TimeFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("TimeFieldParser.parse() requires string object argument"); +} + + +TimeFieldParser.prototype.parseTime =function() { + var time = this.lexer.number(); + if ( time.length > 18) + time = time.substring( time.length - 18); + return parseInt(time); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP RepeatFieldParser . + * @see gov/nist/javax/sdp/parser/RepeatFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function RepeatFieldParser() { + this.classname="RepeatFieldParser"; +} + +RepeatFieldParser.prototype = new SDPParser(); +RepeatFieldParser.prototype.constructor=RepeatFieldParser; + +RepeatFieldParser.prototype.parse =function(repeatFieldString) { + if(typeof repeatFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", repeatFieldString); + this.lexer.match('r'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + + var repeatField = new RepeatField(); + this.lexer.match(LexerCore.prototype.ID); + var repeatInterval = this.lexer.getNextToken(); + this.lexer.SPorHT(); + var typedTime = this.parseTypedTime(repeatInterval.getTokenValue()); + repeatField.setRepeatInterval(typedTime); + this.lexer.match(LexerCore.prototype.ID); + var activeDuration = this.lexer.getNextToken(); + this.lexer.SPorHT(); + typedTime = this.parseTypedTime(activeDuration.getTokenValue()); + repeatField.setActiveDuration(typedTime); + + // The offsets list: + /*Patch 117 */ + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + if (la == '\n' || la == '\r') + break; + this.lexer.match(LexerCore.prototype.ID); + var offsets = this.lexer.getNextToken(); + this.lexer.SPorHT(); + typedTime = this.parseTypedTime(offsets.getTokenValue()); + repeatField.addOffset(typedTime); + } + return repeatField; + } catch(exception) { + throw new SdpException("RepeatFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("RepeatFieldParser.parse() requires string object argument"); +} + + +/** Get the typed time. + * + * @param tokenValue to convert into a typed time. + * @return the typed time + */ +RepeatFieldParser.prototype.parseTypedTime =function(typedTimeString) { + var typedTime = new TypedTime(); + if (typedTimeString.endsWith("d")) { + typedTime.setUnit("d"); + var t = typedTimeString.replace('d', ' '); + typedTime.setTime(parseInt(t.trim())); + } else if (typedTimeString.endsWith("h")) { + typedTime.setUnit("h"); + var t = typedTimeString.replace('h', ' '); + typedTime.setTime(parseInt(t.trim())); + } else if (typedTimeString.endsWith("m")) { + typedTime.setUnit("m"); + var t = typedTimeString.replace('m', ' '); + typedTime.setTime(parseInt(t.trim())); + } else { + typedTime.setUnit("s"); + if (typedTimeString.endsWith("s")) { + var t = typedTimeString.replace('s', ' '); + typedTime.setTime(parseInt(t.trim())); + } else + typedTime.setTime(parseInt(typedTimeString.trim())); + } + return typedTime; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP MediaFieldParser . + * @see gov/nist/javax/sdp/parser/MediaFieldParser.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function MediaFieldParser() { + this.classname="MediaFieldParser"; +} + +MediaFieldParser.prototype = new SDPParser(); +MediaFieldParser.prototype.constructor=MediaFieldParser; + +MediaFieldParser.prototype.parse =function(mediaFieldString) { + if(typeof mediaFieldString == 'string') + { + try { + this.lexer = new LexerCore("charLexer", mediaFieldString); + var mediaField = new MediaField(); + this.lexer.match('m'); + this.lexer.SPorHT(); + this.lexer.match('='); + this.lexer.SPorHT(); + this.lexer.match(LexerCore.prototype.ID); + var media = this.lexer.getNextToken(); + mediaField.setType(media.getTokenValue()); + this.lexer.SPorHT(); + this.lexer.match(LexerCore.prototype.ID); + var port = this.lexer.getNextToken(); + mediaField.setPort(parseInt(port.getTokenValue())); + this.lexer.SPorHT(); + // Some strange media formatting from Sun Ray systems with media + // reported by Emil Ivov and Iain Macdonnell at Sun + if (this.lexer.hasMoreChars() && this.lexer.lookAhead(1) == '\n') + return mediaField; + if (this.lexer.lookAhead(0) == '/') { + // The number of ports is present: + this.lexer.consume(1); + this.lexer.match(LexerCore.prototype.ID); + var portsNumber = this.lexer.getNextToken(); + mediaField.setNports(parseInt(portsNumber.getTokenValue())); + this.lexer.SPorHT(); + } + // proto = token *("/" token) + this.lexer.match(LexerCore.prototype.ID); + var token = this.lexer.getNextToken(); + var transport = token.getTokenValue(); + while (this.lexer.lookAhead(0) == '/') { + this.lexer.consume(1); + this.lexer.match(LexerCore.prototype.ID); + var transportTemp = this.lexer.getNextToken(); + transport = transport + "/" + transportTemp.getTokenValue(); + } + mediaField.setProtocol(transport); + this.lexer.SPorHT(); + + // The formats list: + var formatList = new Array(); + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + if (la == '\n' || la == '\r') + break; + this.lexer.SPorHT(); + //while(lexer.lookAhead(0) == ' ') lexer.consume(1); + this.lexer.match(LexerCore.prototype.ID); + var tok = this.lexer.getNextToken(); + this.lexer.SPorHT(); + var format = tok.getTokenValue().trim(); + if(!format=="") + formatList.push(format); + } + mediaField.setFormats(formatList); + return mediaField; + } catch(exception) { + throw new SdpException("MediaFieldParser.parse(): parsing exception:"+this.lexer.getBuffer() + "at " + this.lexer.getPtr()); + } + } + else throw new SdpException("MediaFieldParser.parse() requires string object argument"); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SDPParserFactory . + * @see gov/nist/javax/sdp/ParserFactory.java + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +/** + * constructor + */ +function SDPParserFactory() { + this.classname="SDPParserFactory"; +} + +SDPParserFactory.prototype.constructor=SDPParserFactory; +SDPParserFactory.prototype.parserTable= new Array(); +SDPParserFactory.prototype.parserTable['a']= AttributeFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['b']= BandwidthFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['c']= ConnectionFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['e']= EmailFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['i']= InformationFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['k']= KeyFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['m']= MediaFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['o']= OriginFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['p']= PhoneFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['v']= VersionFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['r']= RepeatFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['s']= SessionNameFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['t']= TimeFieldParser.prototype.constructor; +SDPParserFactory.prototype.parserTable['u']= SDPParserFactory.prototype.constructor; + +SDPParserFactory.prototype.createParser =function(fieldString) { + if(typeof fieldString == 'string') + { + try { + var i = fieldString.indexOf(Separators.prototype.EQUALS); + if(i == -1) throw new SdpException("SDPParserFactory.createParser(): parsing exception"); + else var fieldName = fieldString.substring(0, i).trim(); + if(this.parserTable[fieldName[0]]!=null) + { + return new this.parserTable[fieldName[0]]; + } + else throw new SdpException("SDPParserFactory.createParse(): could not find parser for " + fieldName) + } catch(exception) { + throw new SdpException("SDPParserFactory.createParse(): could not find parser for " + fieldName) + } + } + else throw new SdpException("SDPParserFactory.createParser() requires string object argument"); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP GenericURI. + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @see gov/nist/javax/sip/address/GenericURI.java + */ + + +function GenericURI(uriString) { + this.classname="GenericURI"; + this.uriString=null; + this.scheme=null; + if(uriString!=null) + { + this.uriString = uriString; + var i = uriString.indexOf(":"); + this.scheme = uriString.substring(0, i); + } +} + +GenericURI.prototype.SIP="sip"; +GenericURI.prototype.SIPS="sips"; +GenericURI.prototype.POSTDIAL = "postdial"; +GenericURI.prototype.PHONE_CONTEXT_TAG ="context-tag"; +GenericURI.prototype.ISUB = "isub"; +GenericURI.prototype.PROVIDER_TAG = "provider-tag"; +GenericURI.prototype.TEL="tel"; + +GenericURI.prototype.encode=function(){ + return this.uriString; +} + +GenericURI.prototype.encodeBuffer=function(buffer){ + buffer=buffer+this.uriString; + return buffer; +} + +GenericURI.prototype.toString=function(){ + return this.encode(); +} + +GenericURI.prototype.getScheme=function(){ + return this.scheme; +} + +GenericURI.prototype.isSipURI=function(){ + if(this instanceof SipUri) + { + return true; + } + else + { + return false; + } +} + +GenericURI.prototype.equals=function(that){ + if (this==that) { + return true; + } + else if (that instanceof URI) { + var o = that; + // This is not sufficient for equality; revert to String equality... + // return this.getScheme().equalsIgnoreCase( o.getScheme() ) + if(this.toString().toLowerCase()==o.toString().toLowerCase()) + { + return true; + } + else + { + return false; + } + } + return false; +} + +GenericURI.prototype.hashCode=function(){ + var hash = 0; + var x=this.toString(); + if(!(x == null || x.value == "")) + { + for (var i = 0; i < x.length; i++) + { + hash = hash * 31 + x.charCodeAt(i); + var MAX_VALUE = 0x7fffffff; + var MIN_VALUE = -0x80000000; + if(hash > MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP UserInfo. + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @see gov/nist/javax/sip/address/UserInfo.java + */ +function UserInfo() { + this.user =null; + this.password=null; + this.userType = null; + this.classname="UserInfo"; +} + +UserInfo.prototype.TELEPHONE_SUBSCRIBER =1; +UserInfo.prototype.USER=2; +UserInfo.prototype.COLON=":"; +UserInfo.prototype.POUND="#"; +UserInfo.prototype.SEMICOLON=";" + +UserInfo.prototype.equals =function(obj){ + if (obj.classname!="UserInfo") { + return false; + } + var other = new UserInfo(); + other=obj; + if (this.userType != other.userType) { + return false; + } + if (this.user!=other.user) { + return false; + } + if (this.password != null && other.password == null) { + return false; + } + if (other.password != null && this.password == null){ + return false; + } + return this.password == other.password; +} + +UserInfo.prototype.encode =function(){ + return this.encodeBuffer("").toString(); +} + +UserInfo.prototype.encodeBuffer =function(buffer){ + var encode=null; + if(this.password!=null) + { + encode=buffer+this.user+this.COLON+this.password; + } + else + { + encode=buffer+this.user; + } + + return encode; +} + +UserInfo.prototype.clearPassword =function(){ + this.password=null; +} + +UserInfo.prototype.getUserType =function(){ + return this.userType; +} + +UserInfo.prototype.getUser =function(){ + return this.user; +} + +UserInfo.prototype.setUser =function(user){ + this.user=user; + if (user != null + && (user.indexOf(this.POUND) >= 0 || user.indexOf(this.SEMICOLON) >= 0)) { + this.setUserType(this.TELEPHONE_SUBSCRIBER); + } else { + this.setUserType(this.USER); + } +} + +UserInfo.prototype.getPassword =function(){ + return this.password; +} + +UserInfo.prototype.setPassword =function(p){ + this.password=p; +} + +UserInfo.prototype.setUserType =function(type){ + if (type != this.USER && type != this.TELEPHONE_SUBSCRIBER) { + console.error("UserInfo:setUserType(): parameter not in range"); + throw "UserInfo:setUserType(): parameter not in range"; + } + this.userType = type; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Authority. + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @see gov/nist/javax/sip/address/Authority.java + */ + +function Authority() { + this.classname="Authority"; + this.userInfo =null; + this.hostPort=new HostPort(); +} + +Authority.prototype.AT="@"; + +Authority.prototype.encode=function(){ + return this.encodeBuffer(""); +} + +Authority.prototype.encodeBuffer=function(buffer){ + if (this.userInfo != null) { + buffer=this.userInfo.encodeBuffer(buffer); + buffer=buffer+this.AT; + buffer=this.hostPort.encodeBuffer(buffer); + } else { + buffer=this.hostPort.encodeBuffer(buffer); + } + return buffer; +} + +Authority.prototype.equals=function(other){ + if (other == null) { + return false; + } + if (other.classname != this.classname) { + return false; + } + var otherAuth = other; + if (this.hostPort!=otherAuth.hostPort) { + return false; + } + if (this.userInfo != null && otherAuth.userInfo != null) { + if (this.userInfo!=otherAuth.userInfo) { + return false; + } + } + return true; +} + +Authority.prototype.getHostPort=function(){ + return this.hostPort; +} + +Authority.prototype.getUserInfo=function(){ + return this.userInfo; +} + +Authority.prototype.getPassword=function(){ + if (this.userInfo == null) + { + return null; + } + else + { + return this.userInfo.password; + } +} + +Authority.prototype.getUser=function(){ + return this.userInfo != null ? this.userInfo.user : null; +} + +Authority.prototype.getHost=function(){ + if (this.hostPort == null) + { + return null; + } + else + { + return this.hostPort.getHost(); + } +} + +Authority.prototype.getPort=function(){ + if (this.hostPort == null) + { + return -1; + } + else + { + return this.hostPort.getPort(); + } +} + +Authority.prototype.removePort=function(){ + if (this.hostPort != null) + { + this.hostPort.removePort(); + } +} + +Authority.prototype.setPassword=function(passwd){ + if (this.userInfo == null) + { + this.userInfo = new UserInfo(); + } + this.userInfo.setPassword(passwd); +} + +Authority.prototype.setUser=function(user){ + if (this.userInfo == null) + { + this.userInfo = new UserInfo(); + } + this.userInfo.setUser(user); +} + +Authority.prototype.setHost=function(host){ + if (this.hostPort == null) + { + this.hostPort = new HostPort(); + } + this.hostPort.setHost(host); +} + +Authority.prototype.setPort=function(port){ + if (this.hostPort == null) + { + this.hostPort = new HostPort(); + } + this.hostPort.setPort(port); +} + +Authority.prototype.setHostPort=function(h){ + this.hostPort=h; +} + +Authority.prototype.setUserInfo=function(u){ + this.userInfo=u; +} + +Authority.prototype.removeUserInfo=function(){ + this.userInfo=null; +} + +Authority.prototype.hashCode=function(){ + if ( this.hostPort == null ) + { + console.error("Authority:hashCode(): null hostPort cannot compute hashcode"); + throw "Authority:hashCode(): null hostPort cannot compute hashcode"; + } + return this.hostPort.encode().hashCode(); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP TelephoneNumber. + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @see gov/nist/javax/sip/address/TelephoneNumber.java + */ + +function TelephoneNumber() { + this.classname="TelephoneNumber"; + this.isglobal=null; + this.phoneNumber=null; + this.parameters=new NameValueList(); +} + +TelephoneNumber.prototype.POSTDIAL = "postdial"; +TelephoneNumber.prototype.PHONE_CONTEXT_TAG ="context-tag"; +TelephoneNumber.prototype.ISUB = "isub"; +TelephoneNumber.prototype.PROVIDER_TAG = "provider-tag"; +TelephoneNumber.prototype.SEMICOLON=";"; + +TelephoneNumber.prototype.deleteParm=function(name){ + this.parameters.delet(name); +} + +TelephoneNumber.prototype.getPhoneNumber=function(){ + return this.phoneNumber; +} + +TelephoneNumber.prototype.getPostDial=function(){ + return this.parameters.getValue(this.POSTDIAL); +} + +TelephoneNumber.prototype.getIsdnSubaddress=function(){ + return this.parameters.getValue(this.ISUB); +} + +TelephoneNumber.prototype.hasPostDial=function(){ + if(this.parameters.getValue(this.POSTDIAL) != null) + { + return true; + } + else + { + return false; + } +} + +TelephoneNumber.prototype.hasParm=function(pname){ + if(this.parameters.hasNameValue(pname)) + { + return true; + } + else + { + return false; + } +} + +TelephoneNumber.prototype.hasIsdnSubaddress=function(){ + if(this.hasParm(this.ISUB)) + { + return true; + } + else + { + return false; + } +} + +TelephoneNumber.prototype.isGlobal=function(){ + return this.isglobal; +} + +TelephoneNumber.prototype.removePostDial=function(){ + this.parameters.delet(this.POSTDIAL); +} + +TelephoneNumber.prototype.removeIsdnSubaddress=function(){ + this.deleteParm(this.ISUB); +} + +TelephoneNumber.prototype.setParameters=function(p){ + this.parameters=p; +} + +TelephoneNumber.prototype.setGlobal=function(g){ + this.isglobal=g; +} + +TelephoneNumber.prototype.setPostDial=function(p){ + var nv=new NameValue(this.POSTDIAL,p); + this.parameters.set_nv(nv); +} + +TelephoneNumber.prototype.setParm=function(name, value){ + var nv = new NameValue(name, value); + this.parameters.set_nv(nv); +} + +TelephoneNumber.prototype.setIsdnSubaddress=function(isub){ + this.setParm(this.ISUB, isub); +} + +TelephoneNumber.prototype.setPhoneNumber=function(num){ + this.phoneNumber=num; +} + +TelephoneNumber.prototype.encode=function(){ + return this.encodeBuffer("").toString(); +} + +TelephoneNumber.prototype.encodeBuffer=function(buffer){ + if (this.isglobal) + { + buffer=buffer+"+"; + } + buffer=buffer+this.phoneNumber; + if (this.parameters.hmap.length!=0) { + buffer=buffer+this.SEMICOLON; + buffer=this.parameters.encodeBuffer(buffer); + } + return buffer; +} + +TelephoneNumber.prototype.getParameter=function(name){ + var val = this.parameters.getValue(name); + if (val == null) + { + return null; + } + if (val instanceof GenericObject) + { + return val.encode(); + } + else + { + return val.toString(); + } +} + +TelephoneNumber.prototype.getParameterNames=function(){ + return this.parameters.getNames(); +} + +TelephoneNumber.prototype.removeParameter=function(parameter){ + this.parameters.delet(parameter); +} + +TelephoneNumber.prototype.setParameter=function(name, value){ + var nv = new NameValue(name, value); + this.parameters.set_nv(nv); +} + +TelephoneNumber.prototype.getParameters=function(){ + return this.parameters; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SipUri. + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @see gov/nist/javax/sip/address/SipUri.java + */ + +function SipUri() { + this.classname="SipUri"; + this.authority=new Authority(); + this.uriParms=new NameValueList(); + this.qheaders=new NameValueList; + this.telephoneSubscriber=new TelephoneNumber(); + this.scheme = "sip"; + this.qheaders.setSeparator("&"); +} + +SipUri.prototype = new GenericURI(); +SipUri.prototype.constructor=SipUri; +SipUri.prototype.SIP="sip"; +SipUri.prototype.SIPS="sips"; +SipUri.prototype.COLON=":"; +SipUri.prototype.SEMICOLON=";"; +SipUri.prototype.QUESTION="?"; +SipUri.prototype.AT="@"; +SipUri.prototype.METHOD="method"; +SipUri.prototype.USER = "user"; +SipUri.prototype.PHONE="phone"; +SipUri.prototype.TTL="ttl"; +SipUri.prototype.MADDR="maddr"; +SipUri.prototype.TRANSPORT="transport"; +SipUri.prototype.LR="lr"; +SipUri.prototype.GRUU="gr"; + +/** Constructor given the scheme. + * The scheme must be either Sip or Sips + */ +SipUri.prototype.setScheme =function(scheme){ + if (scheme.toLowerCase()!= this.SIP + &&scheme.toLowerCase()!= this.SIPS) + { + console.error("SipUri:setScheme(): bad scheme " + scheme); + throw "SipUri:setScheme(): bad scheme " + scheme; + } + this.scheme = scheme.toLowerCase(); +} + +SipUri.prototype.getScheme =function(){ + return this.scheme; +} + +SipUri.prototype.clearUriParms =function(){ + this.uriParms = new NameValueList(); +} + +SipUri.prototype.clearPassword =function(){ + if (this.authority != null) { + var userInfo = this.authority.getUserInfo(); + if (userInfo != null) + { + userInfo.clearPassword(); + } + } +} +SipUri.prototype.getAuthority =function(){ + return this.authority; +} + +SipUri.prototype.clearQheaders =function(){ + this.qheaders = new NameValueList(); +} + +SipUri.prototype.equals =function(that){ + + // Shortcut for same object + if (that==this) { + return true; + } + + if (that instanceof SipUri) { + var a = this; + var b = that; + + // A SIP and SIPS URI are never equivalent + if ( a.isSecure() ^ b.isSecure() ) + { + return false; + } + // For two URIs to be equal, the user, password, host, and port + // components must match; comparison of userinfo is case-sensitive + if (a.getUser()==null ^ b.getUser()==null) + { + return false; + } + if (a.getUserPassword()==null ^ b.getUserPassword()==null) + { + return false; + } + if (a.getUser()!=null && (a.getUser()!=b.getUser())) + { + return false; + } + if (a.getUserPassword()!=null && (a.getUserPassword()!=b.getUserPassword())) + { + return false; + } + if (a.getHost() == null ^ b.getHost() == null) + { + return false; + } + if (a.getHost() != null && !a.getHost().equalsIgnoreCase(b.getHost())) + { + return false; + } + if (a.getPort() != b.getPort()) + { + return false; + } + // URI parameters + var array =a.getParameterNames(); + for (var i=0;i MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +AddressImpl.prototype.equals =function(other){ + if (this==other) { + return true; + } + if (other instanceof AddressImpl) { + var o = other; + + // Don't compare display name (?) + if(this.getURI()== o.getURI() ) + { + return true; + } + else + { + return false; + } + } + return false; +} + +AddressImpl.prototype.hasDisplayName =function(){ + if(this.displayName != null) + { + return true; + } + else + { + return false; + } +} + +AddressImpl.prototype.removeDisplayName =function(){ + this.displayName = null; +} + +AddressImpl.prototype.isSIPAddress =function(){ + if(this.address instanceof SipUri) + { + return true; + } + else + { + return false; + } +} + +AddressImpl.prototype.getURI =function(){ + + return this.address; +} +AddressImpl.prototype.isWildcard =function(){ + if(this.addressType == this.WILD_CARD) + { + return true; + } + else + { + return false; + } +} + +AddressImpl.prototype.setURI=function(address){ + this.address = address; + +} + +AddressImpl.prototype.setUser=function(user){ + if(this.address instanceof SipUri) + { + this.address.setUser(user); + } + else + { + this.address = new SipUri(); + this.address.setUser(user); + } +} + +AddressImpl.prototype.setWildCardFlag=function(){ + this.addressType = this.WILD_CARD; + this.address = new SipUri(); + this.address.setUser("*"); +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP address factory. + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * @see gov/nist/javax/sip/address/AddressFactoryImpl.java + * + */ + +function AddressFactoryImpl() { + this.classname="AddressFactoryImpl"; +} + +AddressFactoryImpl.prototype.createAddress =function(){ + if(arguments.length==0) + { + var addressImpl=new AddressImpl(); + return addressImpl; + } + else if(arguments.length==1) + { + if(typeof arguments[0]=="string") + { + var address=arguments[0]; + var smp=this.createAddress_address(address); + return smp; + } + else if(typeof arguments[0]=="object") + { + var uri=arguments[0]; + var addressImpl=this.createAddress_uri(uri); + return addressImpl; + } + } + else if(arguments.length==2) + { + var displayName=arguments[0]; + uri=arguments[1]; + addressImpl=this.createAddress_name_uri(displayName,uri); + return addressImpl; + } + else + { + console.error("AddressFactoryImpl:createAddress(): too many arg"); + throw "AddressFactoryImpl:createAddress(): too many arg"; + } +} + +AddressFactoryImpl.prototype.createAddress_name_uri =function(displayName,uri){ + if (uri == null) + { + console.error("AddressFactoryImpl:createAddress_name_uri(): null uri arg"); + throw "AddressFactoryImpl:createAddress_name_uri(): null uri arg"; + } + var addressImpl = new AddressImpl(); + if (displayName != null) + { + addressImpl.setDisplayName(displayName); + } + addressImpl.setURI(uri); + return addressImpl; +} + +AddressFactoryImpl.prototype.createSipURI =function(){ + if(arguments.length==0) + { + console.error("AddressFactoryImpl:createSipURI(): missing uri arg"); + throw "AddressFactoryImpl:createSipURI(): missing uri arg"; + } + else if(arguments.length==1) + { + var uri=arguments[0]; + if (uri == null) { + console.error("AddressFactoryImpl:createSipURI(): null uri arg"); + throw "AddressFactoryImpl:createSipURI(): null uri arg"; + } + var smp = new StringMsgParser(); + var sipUri = smp.parseSIPUrl(uri); + return sipUri; + } + else if(arguments.length==2) + { + var user=arguments[0]; + var host=arguments[1]; + sipUri=this.createSipURI_user_host(user, host); + return sipUri; + } +} + +AddressFactoryImpl.prototype.createSipURI_user_host =function(user, host){ + if (host == null) { + console.error("AddressFactoryImpl:createSipURI_user_host(): null host arg"); + throw "AddressFactoryImpl:createSipURI_user_host(): null host arg"; + } + var uriString = "sip:"; + if (user != null) { + uriString=uriString+user+"@"; + } + //if host is an IPv6 string we should enclose it in sq brackets + if (host.indexOf(':') != host.lastIndexOf(':') + && host.trim().charAt(0) != '[') { + host = '[' + host + ']'; + } + uriString=uriString+host; + var smp = new StringMsgParser(); + var sipUri = smp.parseSIPUrl(uriString.toString()); + return sipUri; +} + +AddressFactoryImpl.prototype.createTelURL =function(uri){ + if (uri == null) { + console.error("AddressFactoryImpl:createTelURL(): null uri arg"); + throw "AddressFactoryImpl:createTelURL(): null uri arg"; + } + var telUrl = "tel:" + uri; + var smp = new StringMsgParser(); + var timp = smp.parseUrl(telUrl); + return timp; + +} + +AddressFactoryImpl.prototype.createAddress_uri =function(uri){ + if (uri == null) { + console.error("AddressFactoryImpl:createAddress_uri(): null uri arg"); + throw "AddressFactoryImpl:createAddress_uri(): null uri arg"; + } + var addressImpl = new AddressImpl(); + addressImpl.setURI(uri); + return addressImpl; +} + +AddressFactoryImpl.prototype.createAddress_address =function(address){ + if (address == null) { + console.error("AddressFactoryImpl:createAddress_address(): null address arg"); + throw "AddressFactoryImpl:createAddress_address(): null address arg"; + } + + if (address=="*") { + var addressImpl = new AddressImpl(); + addressImpl.setAddressType(addressImpl.wild_card); + var uri = new SipUri(); + uri.setUser("*"); + addressImpl.setURI(uri); + return addressImpl; + } else { + var addressImpl = new AddressImpl(); + var uri = this.createURI(address); + addressImpl.setURI(uri); + return addressImpl; + } +} + +AddressFactoryImpl.prototype.createURI =function(uri){ + if (uri == null) { + console.error("AddressFactoryImpl:createURI(): null uri arg"); + throw "AddressFactoryImpl:createURI(): null uri arg"; + } + var urlParser = new URLParser(uri); + var scheme = urlParser.peekScheme(); + if (scheme == null) { + console.error("AddressFactoryImpl:createURI(): bad scheme"); + throw "AddressFactoryImpl:createURI(): bad scheme" + } + if (scheme.toLowerCase()=="sip") { + return urlParser.sipURL(true); + } + else if (scheme.toLowerCase()=="sips") { + return urlParser.sipURL(true); + } + else if (scheme.toLowerCase()=="tel") { + return urlParser.telURL(true); + } + return new GenericURI(uri); +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SIPObject . + * @see gov/nist/javax/sip/header/SIPObject.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function SIPObject() { + this.classname="SIPObject"; + //GenericObject.call(this); +} +SIPObject.prototype = new GenericObject(); +SIPObject.prototype.constructor=SIPObject; + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SIPHeader . + * @see gov/nist/javax/sip/header/SIPHeader.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function SIPHeader(headername) { + this.classname="SIPHeader"; + this.headerName=null; + if(headername!=null) + { + this.headerName=headername; + } +} + +SIPHeader.prototype.COLON=":"; +SIPHeader.prototype.SP=" "; +SIPHeader.prototype.NEWLINE="\r\n"; + +SIPHeader.prototype.getHeaderName =function(){ + return this.headerName; +} + +SIPHeader.prototype.getName =function(){ + return this.headerName; +} + +SIPHeader.prototype.setHeaderName =function(hdrname){ + this.headerName=hdrname; +} + +SIPHeader.prototype.getHeaderValue =function(){ + var encodedHdr = null; + encodedHdr = this.encode(); + var buffer = encodedHdr; + while (buffer.length > 0 && buffer.charAt(0) != ':') { + buffer=buffer.substring(1); + } + if (buffer.length > 0) + { + buffer=buffer.substring(1); + } + return buffer.toString().trim(); +} + +SIPHeader.prototype.isHeaderList =function(){ + return false; +} + +SIPHeader.prototype.encode =function(){ + return this.encodeBuffer("").toString(); +} + +SIPHeader.prototype.encodeBuffer =function(buffer){ + buffer=buffer+this.headerName+this.COLON+this.SP; + buffer=this.encodeBodyBuffer(buffer); + buffer=buffer+this.NEWLINE; + return buffer; +} + +SIPHeader.prototype.encodeBody =function(){ +} + +SIPHeader.prototype.encodeBodyBuffer =function(buffer){ + buffer=buffer+this.encodeBody(); + return buffer; +} + +SIPHeader.prototype.getValue =function(){ + return this.getHeaderValue(); +} + +SIPHeader.prototype.hashCode =function(){ + var hash = 0; + var x=this.headerName; + if(!(x == null || x.value == "")) + { + for (var i = 0; i < x.length; i++) + { + hash = hash * 31 + x.charCodeAt(i); + var MAX_VALUE = 0x7fffffff; + var MIN_VALUE = -0x80000000; + if(hash > MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +SIPHeader.prototype.toString =function(){ + return this.encode(); +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SIPHeaderList . + * @see gov/nist/javax/sip/header/SIPHeaderList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function SIPHeaderList() { + this.classname="SIPHeaderList"; + this.prettyEncode = false; + this.hlist=new Array(); + this.myClass=null; + if(arguments.length!=0) + { + var objclass=arguments[0]; + var hname=arguments[1]; + this.headerName = hname; + this.myClass =objclass; + } +} + +SIPHeaderList.prototype = new SIPHeader(); +SIPHeaderList.prototype.constructor=SIPHeaderList; +SIPHeaderList.prototype.NEWLINE="\r\n"; +SIPHeaderList.prototype.WWW_AUTHENTICATE="WWW-Authenticate"; +SIPHeaderList.prototype.PROXY_AUTHENTICATE="Proxy-Authenticate"; +SIPHeaderList.prototype.AUTHORIZATION="Authorization"; +SIPHeaderList.prototype.PROXY_AUTHORIZATION="Proxy-Authorization"; +SIPHeaderList.prototype.ROUTE="Route"; +SIPHeaderList.prototype.RECORD_ROUTE="Record-Route"; +SIPHeaderList.prototype.VIA="Via"; +SIPHeaderList.prototype.COLON=":"; +SIPHeaderList.prototype.SP=" "; +SIPHeaderList.prototype.COMMA=","; +SIPHeaderList.prototype.SEMICOLON=";"; + +SIPHeaderList.prototype.getName =function(){ + return this.headerName; +} + +SIPHeaderList.prototype.add =function(){ + if(arguments.length==1) + { + var objectToAdd=arguments[0]; + var l=null; + for(var i=0;i MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +SIPHeaderList.prototype.set =function(position, sipHeader){ + var x=this.hlist[position]; + this.hlist[position]=sipHeader; + return x; +} +SIPHeaderList.prototype.setPrettyEncode =function(flag){ + this.prettyEncode=flag; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ParametersHeader . + * @see gov/nist/javax/sip/header/ParametersHeader.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ParametersHeader(hdrName, sync) { + this.classname="ParametersHeader"; + this.parameters=null; + this.duplicates=null; + this.headerName=null; + if(hdrName==null&&sync==null) + { + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + } + else if(hdrName!=null&&sync==null) + { + this.headerName=hdrName; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + } + else if(hdrName!=null&&sync!=null) + { + this.headerName=hdrName; + this.parameters = new NameValueList(sync); + this.duplicates = new DuplicateNameValueList(); + } +} + +ParametersHeader.prototype = new SIPHeader(); +ParametersHeader.prototype.constructor=ParametersHeader; + +ParametersHeader.prototype.getParameter =function(name){ + return this.parameters.getParameter(name); +} + +ParametersHeader.prototype.getParameterValue =function(name){ + return this.parameters.getValue(name); +} + +ParametersHeader.prototype.getParameterNames =function(){ + return this.parameters.getNames(); +} + +ParametersHeader.prototype.hasParameters =function(){ + if(this.parameters != null && this.parameters.hmap.length!=0) + { + return true; + } + else + { + return false; + } +} + +ParametersHeader.prototype.removeParameter =function(name){ + this.parameters.delet(name); +} +ParametersHeader.prototype.setQuotedParameter =function(name, value){ + var nv = this.parameters.getNameValue(name); + if (nv != null) + { + nv.setValueAsObject(value); + nv.setQuotedValue(); + } + else { + nv = new NameValue(name, value); + nv.setQuotedValue(); + this.parameters.set_nv(nv); + } +} + +// four fonctions set() with different auguments is integred into one fonction +ParametersHeader.prototype.setParameter =function(name, value){ + if(typeof value=="object") + { + this.parameters.set_name_value(name,val); + } + else if(typeof value=="boolean") + { + var val = new Boolean(value); + this.parameters.set_name_value(name,val); + } + else if(typeof value=="number") + { + var r=value-[value]; + if(r==0) + { + val = new Number(value); + this.parameters.set_name_value(name,val); + } + else + { + val = new Number(value); + var nv = this.parameters.getNameValue(name); + if (nv != null) { + nv.setValueAsObject(val); + } else { + nv = new NameValue(name, val); + this.parameters.set_nv(nv); + } + } + } + else if(typeof value=="string") + { + nv = this.parameters.getNameValue(name); + if (nv != null) { + nv.setValueAsObject(value); + } else { + nv = new NameValue(name, value); + this.parameters.set_nv(nv); + } + } +} + +ParametersHeader.prototype.hasParameter =function(parameterName){ + return this.parameters.hasNameValue(parameterName); +} + +ParametersHeader.prototype.removeParameters =function(){ + this.parameters = new NameValueList(); +} + +ParametersHeader.prototype.getParameters =function(){ + return this.parameters; +} +ParametersHeader.prototype.setParameter_nv =function(nameValue){ + this.parameters.set_nv(nameValue); +} + +ParametersHeader.prototype.setParameters =function(parameters){ + this.parameters = parameters; +} + +ParametersHeader.prototype.getParameterAsNumber =function(parameterName){//i delete the other type of number. they are useless in javascript + if (this.getParameterValue(parameterName) != null) + { + return this.getParameterValue(parameterName)-0; + } + else + { + return -1; + } +} +ParametersHeader.prototype.getParameterAsURI =function(parameterName){ + var val = this.getParameterValue(parameterName); + if (val instanceof GenericURI) + { + return val; + } + else + { + return new GenericURI(val); + } +} + +ParametersHeader.prototype.getParameterAsBoolean =function(parameterName){ + var val = this.getParameterValue(parameterName); + if (val == null) + { + return false; + } + else if (val instanceof Boolean) + { + return val; + } + else if (val instanceof String) + { + var x=new Boolean(val); + return x; + } + else + { + return false; + } +} + +ParametersHeader.prototype.getNameValue =function(parameterName){ + return this.parameters.getNameValue(parameterName); +} + +ParametersHeader.prototype.setMultiParameter_name_value =function(name, value){ + var nv = new NameValue(); + nv.setName(name); + nv.setValue(value); + this.duplicates.set_nv(nv); +} + +ParametersHeader.prototype.setMultiParameter_nv =function(nameValue){ + this.duplicates.set_nv(nameValue); +} + +ParametersHeader.prototype.getMultiParameter =function(name){ + return this.duplicates.getParameter(name); +} + +ParametersHeader.prototype.getMultiParameters =function(){ + return this.duplicates; +} + +ParametersHeader.prototype.getMultiParameterValue =function(name){ + return this.duplicates.getValue(name); +} + +ParametersHeader.prototype.getMultiParameterNames =function(){ + return this.duplicates.getNames(); +} + +ParametersHeader.prototype.hasMultiParameters =function(){ + if(this.duplicates != null && this.duplicates.length!=o) + { + return true; + } + else + { + return false; + } +} + +ParametersHeader.prototype.removeMultiParameter =function(name){ + this.duplicates.delet(name); +} + +ParametersHeader.prototype.hasMultiParameter =function(parameterName){ + return this.duplicates.hasNameValue(parameterName); +} + +ParametersHeader.prototype.removeMultiParameters =function(){ + this.duplicates = new DuplicateNameValueList(); +} + +ParametersHeader.prototype.equalParameters =function(other){ + if (this==other) { + return true; + } + var ary=this.getParameterNames(); + for (var i=0;i 0 && buffer.charAt(0) != ':') { + x=x+1; + chaine=buffer.substring(x); + } + x=x+1; + chaine=buffer.substring(x); + this.value = chaine.toString().trim(); + return this.value; + } +} + +ExtensionHeaderImpl.prototype.encode =function(){ + var encode=""; + encode=encode+this.headerName+this.COLON+this.SP+this.value+this.NEWLINE; + return encode; +} + +ExtensionHeaderImpl.prototype.encodeBody =function(){ + return this.getHeaderValue(); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Server . + * @see gov/nist/javax/sip/header/Server.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Server(to) { + this.classname="Server"; + this.productTokens = new Array(); + this.headerName=this.NAME; +} + +Server.prototype = new SIPHeader(); +Server.prototype.constructor=Server; +Server.prototype.NAME="Server"; + +Server.prototype.encodeProduct =function(){ + var tokens = ""; + for(var i=0;i0) { + this.protocolName = name_and_version.substring(0,slash); + this.protocolVersion = name_and_version.substring( slash+1 ); + } + else + { + console.error("Event:setProtocol(): missing '/' in protocol", 0 ); + throw "Event:setProtocol(): missing '/' in protocol"; + } +} + +Protocol.prototype.getTransport =function(){ + return this.transport; +} + +Protocol.prototype.setProtocolName =function(pn){ + this.protocolName=pn; +} + +Protocol.prototype.setProtocolVersion =function(p){ + this.protocolVersion=p +} + +Protocol.prototype.setTransport =function(t){ + this.transport=t; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Via . + * @see gov/nist/javax/sip/header/Via.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Via() { + this.classname="Via"; + this.sentProtocol=new Protocol(); + this.sentBy=new HostPort(); + this.comment=null; + // Set to true for reSIProcate SIP Over WebSockets workaround http://code.google.com/p/jain-sip/issues/detail?id=35 + this.rPortFlag = true; + this.headerName=this.NAME; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); +} + +Via.prototype = new ParametersHeader(); +Via.prototype.constructor=Via; +Via.prototype.NAME="Via"; +Via.prototype.BRANCH="branch"; +Via.prototype.RECEIVED="received"; +Via.prototype.MADDR="maddr"; +Via.prototype.TTL="ttl"; +Via.prototype.RPORT="rport"; +Via.prototype.SP=" "; +Via.prototype.SEMICOLON=";"; +Via.prototype.LPAREN="("; +Via.prototype.RPAREN=")"; + + +Via.prototype.getProtocolVersion =function(){ + if (this.sentProtocol == null) + { + return null; + } + else + { + return this.sentProtocol.getProtocolVersion(); + } +} + +Via.prototype.getSentProtocol =function(){ + return this.sentProtocol; +} + +Via.prototype.getSentBy =function(){ + return this.sentBy; +} + +Via.prototype.getHop =function(){ + var hop = new HopImpl(this.sentBy.getHost().getHostname()); + return hop; +} + +Via.prototype.getViaParms =function(){ + return this.parameters; +} +Via.prototype.hasPort =function(){ + return this.getSentBy().hasPort(); +} + +Via.prototype.hasComment =function(){ + if(this.comment != null) + { + return true; + } + else + { + return false; + } +} + +Via.prototype.removePort =function(){ + this.sentBy.removePort(); +} + +Via.prototype.removeComment =function(){ + this.comment = null; +} + +Via.prototype.setProtocolVersion =function(){ + if (this.sentProtocol == null) + { + this.sentProtocol = new Protocol(); + } + this.sentProtocol.setProtocolVersion(this.protocolVersion); +} + +Via.prototype.setHost =function(host){ + if(typeof host=="object") + { + if (this.sentBy == null) { + this.sentBy = new HostPort(); + } + this.sentBy.setHost(host); + } + else if(typeof host=="string") + { + if (this.sentBy == null) + + { + this.sentBy = new HostPort(); + } + var h = new Host(host); + this.sentBy.setHost(h); + } + +} + +Via.prototype.setSentProtocol =function(s){ + this.sentProtocol = s; +} + +Via.prototype.setSentBy =function(s){ + this.sentBy = s; +} + +Via.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +Via.prototype.encodeBodyBuffer =function(buffer){ + buffer=this.sentProtocol.encodeBuffer(buffer); + buffer=buffer+this.SP; + buffer=this.sentBy.encodeBuffer(buffer); + if (this.parameters.length!=0) { + buffer=buffer+this.SEMICOLON; + buffer=this.parameters.encodeBuffer(buffer); + } + if (this.comment != null) { + buffer=buffer+this.SP+this.LPAREN+this.comment+this.RPAREN; + } + // https://code.google.com/p/jain-sip/issues/detail?id=130 rport is present 2 times on 407 Authentication Required + if (this.rPortFlag && this.getParameter(this.RPORT) == null) + { + buffer=buffer+";rport" + } + return buffer; +} + +Via.prototype.getHost =function(){ + if (this.sentBy == null) + return null; + else { + var host = this.sentBy.getHost(); + if (host == null) + { + return null; + } + else + { + return host.getHostname(); + } + } +} + +Via.prototype.setPort =function(port){ + if ( port!=-1 && (port<1 || port>65535)) { + console.error("Via:setPort(): port value out of range -1, [1..65535]"); + throw "Via:setPort(): port value out of range -1, [1..65535]"; + } + if (this.sentBy == null) + { + this.sentBy = new HostPort(); + } + this.sentBy.setPort(port); +} + +Via.prototype.setRPort =function(){ + this.rPortFlag = true; +} + +Via.prototype.getPort =function(){ + if (this.sentBy == null) + { + return -1; + } + return this.sentBy.getPort(); +} + +Via.prototype.getRPort =function(){ + var strRport = this.getParameter(this.RPORT); + if (strRport != null && ! strRport.equals("")) + { + var x=strRport-0; + return x; + } + else + { + return -1; + } +} + +Via.prototype.getTransport =function(){ + if (this.sentProtocol == null) + { + return null; + } + return this.sentProtocol.getTransport(); +} + +Via.prototype.setTransport =function(transport){ + if (transport == null) + { + console.error("Via:setTransport(): the transport parameter is null"); + throw "Via:setTransport(): the transport parameter is null"; + } + if (this.sentProtocol == null) + { + this.sentProtocol = new Protocol(); + } + this.sentProtocol.setTransport(transport); +} + +Via.prototype.getProtocol =function(){ + if (this.sentProtocol == null) + { + return null; + } + return this.sentProtocol.getProtocol(); +} + +Via.prototype.setProtocol =function(protocol){ + if (protocol == null) + { + console.error("Via:setProtocol(): the transport parameter is null"); + throw "Via:setProtocol(): the protocol parameter is null"; + } + if (this.sentProtocol == null) + { + this.sentProtocol = new Protocol(); + } + this.sentProtocol.setProtocol(protocol); +} + +Via.prototype.getTTL =function(){ + var ttl = this.getParameterAsNumber(this.TTL); + return ttl; +} + +Via.prototype.setTTL =function(ttl){ + if (ttl < 0 && ttl != -1) + { + console.error("Via:setTTL(): the ttl parameter is < 0"); + throw "Via:setTTL(): the ttl parameter is < 0"; + } + ttl=ttl-0; + this.setParameter_nv(new NameValue(this.TTL, ttl)); +} + +Via.prototype.getMAddr =function(){ + return this.getParameter(this.MADDR); +} + +Via.prototype.setMAddr =function(mAddr){ + if (mAddr == null) + { + console.error("Via:setMAddr(): the mAddr parameter is < 0"); + throw "Via:setMAddr(): the mAddr parameter is < 0"; + } + var host = new Host(); + host.setAddress(mAddr); + var nameValue = new NameValue(this.MADDR, host); + this.setParameter_nv(nameValue); +} + +Via.prototype.getReceived =function(){ + return this.getParameter(this.RECEIVED); +} + +Via.prototype.setReceived =function(received){ + if (received == null) + { + console.error("Via:setReceived(): the received parameter is < 0"); + throw "Via:setReceived(): the received parameter is < 0"; + } + this.setParameter(this.RECEIVED, received); +} + +Via.prototype.getBranch =function(){ + return this.getParameter(this.BRANCH); +} + +Via.prototype.setBranch =function(branch){ + if (branch == null || branch.length==0) + { + console.error("Via:setBranch(): branch parameter is null or length 0"); + throw "Via:setBranch(): branch parameter is null or length 0"; + } + this.setParameter(this.BRANCH, branch); +} + +Via.prototype.getSentByField =function(){ + if(this.sentBy != null) + { + return this.sentBy.encode(); + } + return null; +} +Via.prototype.getSentProtocolField =function(){ + if(this.sentProtocol != null) + { + return this.sentProtocol.encode(); + } + return null; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Contact . + * @see gov/nist/javax/sip/header/Contact.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Contact() { + this.classname="Contact"; + this.headerName=this.NAME; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.address=new AddressImpl(); + this.Q = this.Q; + this.contactList=new ContactList(); + this.wildCardFlag=null; +} + +Contact.prototype = new AddressParametersHeader(); +Contact.prototype.constructor=Contact; +Contact.prototype.NAME="Contact"; +Contact.prototype.ACTION="action"; +Contact.prototype.PROXY="proxy"; +Contact.prototype.REDIRECT="redirect"; +Contact.prototype.EXPIRES="expires"; +Contact.prototype.Q="q"; +Contact.prototype.NAME_ADDR=1; +Contact.prototype.SEMICOLON=";"; +Contact.prototype.SIP_INSTANCE="+sip.instance"; +Contact.prototype.PUB_GRUU="pub-gruu"; +Contact.prototype.TEMP_GRUU="temp-gruu"; + +Contact.prototype.setParameter =function(name,value){ + var nv = this.parameters.getNameValue(name); + if (nv != null) + { + nv.setValueAsObject(value); + } + else + { + nv = new NameValue(name, value); + if (name.toLowerCase()=="methods") + { + nv.setQuotedValue(); + } + this.parameters.set_nv(nv); + } +} + +Contact.prototype.encodeBody =function(){ + return this.encodeBodyBuffer(""); +} + +Contact.prototype.encodeBodyBuffer =function(buffer){ + if (this.wildCardFlag) { + buffer=buffer+"*"; + } + else { + if (this.address.getAddressType() == this.NAME_ADDR) + { + buffer=this.address.encodeBuffer(buffer); + } + else + { + buffer=buffer+"<"; + buffer=this.address.encodeBuffer(buffer); + buffer=buffer+">"; + } + if (this.parameters.hmap.length!=0) + { + buffer=buffer+this.SEMICOLON; + buffer=this.parameters.encodeBuffer(buffer); + } + } + return buffer; +} + +Contact.prototype.getContactList =function(){ + return this.contactList; +} + +Contact.prototype.getWildCardFlag =function(){ + return this.wildCardFlag; +} + +Contact.prototype.getAddress =function(){ + return this.address; +} + +Contact.prototype.getContactParms =function(){ + return this.parameters; +} + +Contact.prototype.getExpires =function(){ + return this.getParameterAsNumber(this.EXPIRES); +} + +Contact.prototype.setExpires =function(expiryDeltaSeconds){ + var deltaSeconds = expiryDeltaSeconds-0; + this.parameters.set_name_value(this.EXPIRES, deltaSeconds); +} + +Contact.prototype.getQValue =function(){ + return this.getParameterAsNumber(Q); +} + +Contact.prototype.setContactList =function(cl){ + this.contactList=cl; +} + +Contact.prototype.setWildCardFlag =function(w){ + this.wildCardFlag = true; + this.address = new AddressImpl(); + this.address.setWildCardFlag(); +} + +Contact.prototype.setAddress =function(address){ + if (address == null) + { + console.error("CSeq:setAddress(): the address parameter is null"); + throw "CSeq:setAddress(): the address parameter is null"; + } + this.address = address; + this.wildCardFlag = false; +} + +Contact.prototype.setQValue =function(qValue){ + if (qValue != -1 && (qValue < 0 || qValue > 1)) + { + console.error("CSeq:setQValue(): the qValue is not between 0 and 1"); + throw "CSeq:setQValue(): Jthe qValue is not between 0 and 1"; + } + var qv=new Number(qValue); + this.parameters.set_name_value(Q, qv); +} + +Contact.prototype.setWildCard =function(){ + this.setWildCardFlag(true); +} +Contact.prototype.isWildCard =function(){ + return this.address.isWildcard(); +} + +Contact.prototype.removeSipInstanceParam =function(){ + if (this.parameters != null) + { + this.parameters.delet(this.SIP_INSTANCE); + } +} + +Contact.prototype.getSipInstanceParam =function(){ + return this.parameters.getValue(this.SIP_INSTANCE); +} + +Contact.prototype.setSipInstanceParam =function(value){ + this.parameters.set_name_value(this.SIP_INSTANCE, value); +} + +Contact.prototype.removePubGruuParam =function(){ + if (this.parameters != null) + { + this.parameters.delet(this.PUB_GRUU); + } +} + +Contact.prototype.getPubGruuParam =function(){ + return this.parameters.getValue(this.PUB_GRUU); +} + +Contact.prototype.setPubGruuParam =function(value){ + this.parameters.set_name_value(this.PUB_GRUU, value); +} + +Contact.prototype.removeTempGruuParam =function(){ + if (this.parameters != null) + { + this.parameters.delet(this.TEMP_GRUU); + } +} + +Contact.prototype.getTempGruuParam =function(){ + return this.parameters.getValue(this.TEMP_GRUU); +} + +Contact.prototype.setTempGruuParam =function(value){ + this.parameters.set_name_value(this.TEMP_GRUU, value); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP MediaRange . + * @see gov/nist/javax/sip/header/MediaRange.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function MediaRange() { + this.classname="MediaRange"; + this.type=null; + this.subtype=null; +} + +MediaRange.prototype = new SIPObject(); +MediaRange.prototype.constructor=MediaRange; +MediaRange.prototype.SLASH="/" + +MediaRange.prototype.getType =function(){ + return this.type; +} + +MediaRange.prototype.getSubtype =function(){ + return this.subtype; +} + +MediaRange.prototype.setType =function(t){ + this.type=t; +} + +MediaRange.prototype.setSubtype =function(s){ + this.subtype=s; +} + +MediaRange.prototype.encode =function(){ + return this.encodeBuffer("").toString(); +} + +MediaRange.prototype.encodeBuffer =function(buffer){ + buffer=buffer+this.type+this.SLASH+this.subtype; + return buffer; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AuthenticationHeader . + * @see gov/nist/javax/sip/header/AuthenticationHeader.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function AuthenticationHeader(name) { + this.classname="AuthenticationHeader"; + this.headerName=null; + this.scheme=null; + if(name==null) + { + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.parameters.setSeparator(this.COMMA); + } + else + { + this.headerName=name; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.parameters.setSeparator(this.COMMA); + this.scheme = this.DIGEST; + } +} + +AuthenticationHeader.prototype = new ParametersHeader(); +AuthenticationHeader.prototype.constructor=AuthenticationHeader; +AuthenticationHeader.prototype.DOMAIN = "domain"; +AuthenticationHeader.prototype.REALM = "realm"; +AuthenticationHeader.prototype.OPAQUE = "opaque"; +AuthenticationHeader.prototype.ALGORITHM = "algorithm"; +AuthenticationHeader.prototype.QOP = "qop"; +AuthenticationHeader.prototype.STALE = "stale"; +AuthenticationHeader.prototype.SIGNATURE = "signature"; +AuthenticationHeader.prototype.RESPONSE = "response"; +AuthenticationHeader.prototype.SIGNED_BY = "signed-by"; +AuthenticationHeader.prototype.NC = "nc"; +AuthenticationHeader.prototype.URI = "uri"; +AuthenticationHeader.prototype.USERNAME = "username"; +AuthenticationHeader.prototype.CNONCE = "cnonce"; +AuthenticationHeader.prototype.NONCE = "nonce"; +AuthenticationHeader.prototype.IK = "ik"; +AuthenticationHeader.prototype.CK = "ck"; +AuthenticationHeader.prototype.INTEGRITY_PROTECTED = "integrity-protected"; +AuthenticationHeader.prototype.COMMA = ","; +AuthenticationHeader.prototype.DIGEST = "Digest"; +AuthenticationHeader.prototype.DOUBLE_QUOTE = "\""; +AuthenticationHeader.prototype.SP = " "; +//AuthenticationHeader.prototype.CK = "ck"; + +AuthenticationHeader.prototype.setParameter =function(name,value){ + var nv = this.parameters.getNameValue(name.toLowerCase()); + if (nv == null) + { + nv = new NameValue(name, value); + if (name.toLowerCase()==this.QOP + || name.toLowerCase()==this.REALM + || name.toLowerCase()==this.CNONCE + || name.toLowerCase()==this.NONCE + || name.toLowerCase()==this.USERNAME + || name.toLowerCase()==this.DOMAIN + || name.toLowerCase()==this.OPAQUE + || name.toLowerCase()==this.NEXT_NONCE + || name.toLowerCase()==this.URI + || name.toLowerCase()==this.RESPONSE + ||name.toLowerCase()==this.IK + || name.toLowerCase()==this.CK + || name.toLowerCase()==this.INTEGRITY_PROTECTED) + { + if (((this instanceof Authorization) || (this instanceof ProxyAuthorization)) + && name.toLowerCase()==this.QOP) { + // NOP, QOP not quoted in authorization headers + } + else + { + nv.setQuotedValue(); + } + if (value == null) + { + console.error("AuthenticationHeader:setParameter(): null value"); + throw "AuthenticationHeader:setParameter(): null value"; + } + if (value.charAt(0)==this.DOUBLE_QUOTE) + { + console.error("AuthenticationHeader:setParameter(): unexpected DOUBLE_QUOTE"); + throw "AuthenticationHeader:setParameter(): unexpected DOUBLE_QUOTE"; + } + } + this.parameters.set_nv(nv); + } + else + { + nv.setValueAsObject(value); + } + +} + +AuthenticationHeader.prototype.setChallenge =function(challenge){ + this.scheme = challenge.scheme; + this.parameters = challenge.authParams; +} + +AuthenticationHeader.prototype.encodeBody =function(){ + this.parameters.setSeparator(this.COMMA); + return this.scheme + this.SP + this.parameters.encode(); +} + +AuthenticationHeader.prototype.setScheme =function(scheme){ + this.scheme=scheme; +} + +AuthenticationHeader.prototype.getScheme =function(){ + return this.scheme; +} + +AuthenticationHeader.prototype.setRealm =function(realm){ + if (realm == null) + { + console.error("AuthenticationHeader:setRealm(): the realm parameter is null"); + throw "AuthenticationHeader:setRealm(): the realm parameter is null"; + } + this.setParameter(this.REALM, realm); +} + +AuthenticationHeader.prototype.getRealm =function(){ + return this.getParameter(this.REALM); +} + +AuthenticationHeader.prototype.setNonce =function(nonce){ + if (nonce == null) + { + console.error("AuthenticationHeader:setNonce(): the nonce parameter is null"); + throw "AuthenticationHeader:setNonce(): the nonce parameter is null"; + } + this.setParameter(this.NONCE, nonce); +} + +AuthenticationHeader.prototype.getNonce =function(){ + return this.getParameter(this.NONCE); +} + +AuthenticationHeader.prototype.setURI =function(uri){ + if (uri != null) + { + var nv = new NameValue(this.URI, uri); + nv.setQuotedValue(); + this.parameters.set_nv(nv); + } + else + { + console.error("AuthenticationHeader:setNonce(): the uri parameter is null"); + throw "AuthenticationHeader:setURI(): the uri parameter is null"; + } +} + +AuthenticationHeader.prototype.getURI =function(){ + return this.getParameterAsURI(this.URI); +} + +AuthenticationHeader.prototype.setAlgorithm =function(algorithm){ + if (algorithm == null) + { + console.error("AuthenticationHeader:setAlgorithm(): the algorithm parameter is null"); + throw "AuthenticationHeader:setAlgorithm(): the algorithm parameter is null"; + } + this.setParameter(this.ALGORITHM, algorithm); +} + +AuthenticationHeader.prototype.getAlgorithm =function(){ + return this.getParameter(this.ALGORITHM); +} + +AuthenticationHeader.prototype.setQop =function(qop){ + if (qop == null) + { + console.error("AuthenticationHeader:setQop(): the qop parameter is null"); + throw "AuthenticationHeader:setQop(): the qop parameter is null"; + } + this.setParameter(this.QOP, qop); +} + +AuthenticationHeader.prototype.getQop =function(){ + return this.getParameter(this.QOP); +} + +AuthenticationHeader.prototype.setOpaque =function(opaque){ + if (opaque == null) + { + console.error("AuthenticationHeader:setOpaque(): the opaque parameter is null"); + throw "AuthenticationHeader:setOpaque(): the opaque parameter is null"; + } + this.setParameter(this.OPAQUE, opaque); +} + +AuthenticationHeader.prototype.getOpaque =function(){ + return this.getParameter(this.OPAQUE); +} + +AuthenticationHeader.prototype.setDomain =function(domain){ + if (domain == null) + { + console.error("AuthenticationHeader:setDomain(): the domain parameter is null"); + throw "AuthenticationHeader:setDomain(): the opaque parameter is null"; + } + this.setParameter(this.DOMAIN, domain); +} + +AuthenticationHeader.prototype.getDomain =function(){ + return this.getParameter(this.DOMAIN); +} + +AuthenticationHeader.prototype.setStale =function(stale){ + this.parameters.set_nv(new NameValue(this.STALE, new Boolean(stale))); +} + +AuthenticationHeader.prototype.isStale =function(){ + return this.getParameterAsBoolean(this.STALE); +} + +AuthenticationHeader.prototype.setCNonce =function(cnonce){ + this.setParameter(this.CNONCE, cnonce); +} + +AuthenticationHeader.prototype.getCNonce =function(){ + return this.getParameter(this.CNONCE); +} + +AuthenticationHeader.prototype.getNonceCount =function(){ + return this.getParameterAsNumber(this.NC); +} + +AuthenticationHeader.prototype.setNonceCount =function(param){ + if (param < 0) + { + console.error("AuthenticationHeader:setNonceCount(): bad parameter"); + throw "AuthenticationHeader:setNonceCount(): bad parameter"; + } + var nc = new String(param); + var base = "00000000"; + nc = base.substring(0, 8 - nc.length) + nc; + this.setParameter(this.NC, nc); +} + +AuthenticationHeader.prototype.getResponse =function(){ + return this.getParameter(this.RESPONSE); +} + +AuthenticationHeader.prototype.setResponse =function(response){ + if (response == null) + { + console.error("AuthenticationHeader:setResponse(): the domain parameter is null"); + throw "AuthenticationHeader:setResponse(): the opaque parameter is null"; + } + this.setParameter(this.RESPONSE, response); +} + +AuthenticationHeader.prototype.getUsername =function(){ + return this.getParameter(this.USERNAME); +} + +AuthenticationHeader.prototype.setUsername =function(username){ + this.setParameter(this.USERNAME, username); +} + +AuthenticationHeader.prototype.setIK =function(ik){ + if (ik == null) + { + console.error("AuthenticationHeader:setIK(): the auth-param IK parameter is null"); + throw "AuthenticationHeader:setIK(): the auth-param IK parameter is null"; + } + this.setParameter(this.IK, ik); +} + +AuthenticationHeader.prototype.getIK =function(){ + return this.getParameter(this.IK); +} + +AuthenticationHeader.prototype.setCK =function(ck){ + if (ck == null) + { + console.error("AuthenticationHeader:setCK() the auth-param CK parameter is null"); + throw "AuthenticationHeader:setCK(): the auth-param CK parameter is null"; + } + this.setParameter(this.CK, ck); +} + +AuthenticationHeader.prototype.getCK =function(){ + return this.getParameter(this.CK); +} + +AuthenticationHeader.prototype.setIntegrityProtected =function(integrityProtected){ + if (integrityProtected == null) + { + console.error("AuthenticationHeader:setIntegrityProtected(): the integrity-protected parameter is null"); + throw "AuthenticationHeader:setIntegrityProtected(): the integrity-protected parameter is null"; + } + this.setParameter(this.INTEGRITY_PROTECTED, integrityProtected); +} + +AuthenticationHeader.prototype.getIntegrityProtected =function(){ + return this.getParameter(this.INTEGRITY_PROTECTED); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP WWWAuthenticate . + * @see gov/nist/javax/sip/header/WWWAuthenticate.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function WWWAuthenticate() { + this.classname="WWWAuthenticate"; + this.headerName=this.NAME; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.parameters.setSeparator(this.COMMA); + this.scheme = this.DIGEST; +} + +WWWAuthenticate.prototype = new AuthenticationHeader(); +WWWAuthenticate.prototype.constructor=WWWAuthenticate; +WWWAuthenticate.prototype.NAME="WWW-Authenticate"; +WWWAuthenticate.prototype.COMMA = ","; +WWWAuthenticate.prototype.DIGEST = "Digest"; + +WWWAuthenticate.prototype.getURI =function(){ + return null +} + +WWWAuthenticate.prototype.setURI =function(uri){ +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Route . + * @see gov/nist/javax/sip/header/Route.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Route(address) { + this.classname="Route"; + if(address==null) + { + this.headerName = this.NAME; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + } + else + { + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.address = address; + } +} + +Route.prototype = new AddressParametersHeader(); +Route.prototype.constructor=Route; +Route.prototype.NAME="Route"; +Route.prototype.NAME_ADDR=1; +Route.prototype.SEMICOLON=";"; + +Route.prototype.hashCode =function(){ + var hash = 0; + var x=this.address.getHostPort().encode().toLowerCase(); + if(!(x == null || x.value == "")) + { + for (var i = 0; i < x.length; i++) + { + hash = hash * 31 + x.charCodeAt(i); + var MAX_VALUE = 0x7fffffff; + var MIN_VALUE = -0x80000000; + if(hash > MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +Route.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +Route.prototype.encodeBodyBuffer =function(buffer){ + var addrFlag = this.address.getAddressType() == this.NAME_ADDR; + if (!addrFlag) { + buffer=buffer+"<"; + buffer=this.address.encodeBuffer(buffer); + buffer=buffer+">"; + } else { + buffer=this.address.encodeBuffer(buffer); + } + if (this.parameters.hmap.length!=0) { + buffer=buffer+this.SEMICOLON; + buffer=this.parameters.encodeBuffer(buffer); + } + return buffer; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ProxyAuthenticate . + * @see gov/nist/javax/sip/header/ProxyAuthenticate.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ProxyAuthenticate() { + this.classname="ProxyAuthenticate"; + this.headerName=this.PROXY_AUTHENTICATE; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.parameters.setSeparator(this.COMMA); + this.scheme = this.DIGEST; +} + +ProxyAuthenticate.prototype = new AuthenticationHeader(); +ProxyAuthenticate.prototype.constructor=ProxyAuthenticate; +ProxyAuthenticate.prototype.PROXY_AUTHENTICATE="Proxy-Authenticate"; +ProxyAuthenticate.prototype.COMMA = ","; +ProxyAuthenticate.prototype.DIGEST = "Digest"; + +ProxyAuthenticate.prototype.getURI =function(){ + return null +} + +ProxyAuthenticate.prototype.setURI =function(uri){ + +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ProxyAuthorization . + * @see gov/nist/javax/sip/header/ProxyAuthorization.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ProxyAuthorization() { + this.classname="ProxyAuthorization"; + this.scheme=null; + this.headerName=this.PROXY_AUTHORIZATION; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.parameters.setSeparator(this.COMMA); + this.scheme = this.DIGEST; +} + +ProxyAuthorization.prototype = new AuthenticationHeader(); +ProxyAuthorization.prototype.constructor=ProxyAuthorization; +ProxyAuthorization.prototype.PROXY_AUTHORIZATION="Proxy-Authorization"; +ProxyAuthorization.prototype.COMMA = ","; +ProxyAuthorization.prototype.DIGEST = "Digest"; +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP StatusLine . + * @see gov/nist/javax/sip/header/StatusLine.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function StatusLine() { + this.classname="StatusLine"; + this.matchStatusClass=null; + this.sipVersion=this.SIP_VERSION_STRING; + this.statusCode=null; + this.reasonPhrase=null; +} + +StatusLine.prototype = new SIPObject(); +StatusLine.prototype.constructor=StatusLine; +StatusLine.prototype.SIP_VERSION_STRING="SIP/2.0"; +StatusLine.prototype.SP=" "; +StatusLine.prototype.NEWLINE="\r\n"; + +StatusLine.prototype.match =function(){ + +} +StatusLine.prototype.setMatchStatusClass =function(flag){ + this.matchStatusClass=flag; +} + +StatusLine.prototype.encode =function(){ + var encoding = this.SIP_VERSION_STRING + this.SP + this.statusCode; + if (this.reasonPhrase != null) + { + encoding =encoding+this.SP + this.reasonPhrase; + } + encoding =encoding+ this.NEWLINE; + return encoding; +} + +StatusLine.prototype.getSipVersion =function(){ + return this.sipVersion; +} + +StatusLine.prototype.getStatusCode =function(){ + return this.statusCode; +} + +StatusLine.prototype.getReasonPhrase =function(){ + return this.reasonPhrase; +} + +StatusLine.prototype.setSipVersion =function(s){ + this.sipVersion=s; +} + +StatusLine.prototype.setStatusCode =function(statusCode){ + this.statusCode=statusCode; +} + +StatusLine.prototype.setReasonPhrase =function(reasonPhrase){ + this.reasonPhrase=reasonPhrase; +} + +StatusLine.prototype.getVersionMajor =function(){ + if (this.sipVersion == null) + { + return null; + } + var major = null; + var slash = false; + for (var i = 0; i < this.sipVersion.length; i++) { + if (this.sipVersion.charAt(i) == ".") + { + slash = false; + } + if (slash) + { + if (major == null) + major = "" + this.sipVersion.charAt(i); + else + major =major+ this.sipVersion.charAt(i); + } + if (this.sipVersion.charAt(i) == "/") + { + slash = true; + } + } + return major; +} + +StatusLine.prototype.getVersionMinor =function(){ + if (this.sipVersion == null) + return null; + var minor = null; + var dot = false; + for (var i = 0; i < this.sipVersion.length; i++) { + if (dot) + { + if (minor == null) + { + minor = "" + this.sipVersion.charAt(i); + } + else + { + minor =minor + this.sipVersion.charAt(i); + } + } + if (this.sipVersion.charAt(i) == '.') + { + dot = true; + } + } + return minor; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Authorization . + * @see gov/nist/javax/sip/header/Authorization.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Authorization() { + this.classname="Authorization"; + this.headerName=this.NAME; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + this.parameters.setSeparator(this.COMMA); + this.scheme = this.DIGEST; +} + +Authorization.prototype = new AuthenticationHeader(); +Authorization.prototype.constructor=Authorization; +Authorization.prototype.NAME = "Authorization"; +Authorization.prototype.COMMA = ","; +Authorization.prototype.DIGEST = "Digest"; +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Allow . + * @see gov/nist/javax/sip/header/Allow.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function Allow (m) { + this.classname="Allow"; + this.method=null; + if(m==null) + { + this.headerName=this.ALLOW; + } + else if(arguments.length==1) + { + this.headerName=this.ALLOW; + this.method=m; + } +} + +Allow.prototype = new SIPHeader(); +Allow.prototype.constructor=Allow; +Allow.prototype.ALLOW="Allow"; + +Allow.prototype.getMethod =function(){ + return this.method; +} + +Allow.prototype.setMethod =function(method){ + if (method == null) + { + console.error("Allow:setMethod(): JAIN-SIP Exception, the method parameter is null."); + throw "Allow:setMethod(): the method parameter is null."; + } + this.method = method; +} + +Allow.prototype.encodeBody =function(){ + return this.method; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP RecordRoute . + * @see gov/nist/javax/sip/header/RecordRoute.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function RecordRoute(address) { + this.classname="RecordRoute"; + if(address==null) + { + this.headerName=this.RECORD_ROUTE; + this.address=new AddressImpl(); + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + } + else + { + this.headerName=this.NAME; + this.address=address; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + } +} + +RecordRoute.prototype = new AddressParametersHeader(); +RecordRoute.prototype.constructor=RecordRoute; +RecordRoute.prototype.NAME="Record-Route"; +RecordRoute.prototype.RECORD_ROUTE="Record-Route"; +RecordRoute.prototype.ADDRESS_SPEC = 2; +RecordRoute.prototype.LESS_THAN="<"; +RecordRoute.prototype.GREATER_THAN=">"; +RecordRoute.prototype.SEMICOLON=";"; + +RecordRoute.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +RecordRoute.prototype.encodeBodyBuffer =function(buffer){ + if (this.address.getAddressType() == this.ADDRESS_SPEC) { + buffer=buffer+this.LESS_THAN; + } + buffer=this.address.encodeBuffer(buffer); + if (this.address.getAddressType() == this.ADDRESS_SPEC) { + buffer=buffer+this.GREATER_THAN; + } + + if (this.parameters.hmap.length!=0) { + buffer=buffer+this.SEMICOLON; + buffer=this.parameters.encodeBuffer(buffer); + } + return buffer; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP MaxForwards . + * @see gov/nist/javax/sip/header/MaxForwards.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function MaxForwards(m) { + this.classname="MaxForwards"; + this.headerName=this.NAME; + this.maxForwards=null; + if(m==null) + { + this.headerName=this.NAME; + } + else + { + this.headerName=this.NAME; + this.setMaxForwards( m ); + } +} + +MaxForwards.prototype = new SIPHeader(); +MaxForwards.prototype.constructor=MaxForwards; +MaxForwards.prototype.NAME="Max-Forwards"; + +MaxForwards.prototype.getMaxForwards =function(){ + return this.maxForwards; +} + +MaxForwards.prototype.setMaxForwards =function(maxForwards){ + if (maxForwards < 0 || maxForwards > 255) + { + console.error("MaxForwards:setMaxForwards(): bad max forwards value " + maxForwards); + throw "MaxForwards:setMaxForwards(): bad max forwards value " + maxForwards; + } + this.maxForwards = maxForwards; +} + +MaxForwards.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +MaxForwards.prototype.encodeBodyBuffer =function(buffer){ + buffer=buffer+this.maxForwards; + return buffer; +} + +MaxForwards.prototype.hasReachedZero =function(){ + if(this.maxForwards == 0) + { + return true; + } + else + { + return false; + } +} + +MaxForwards.prototype.decrementMaxForwards =function(){ + if (this.maxForwards > 0) + { + this.maxForwards--; + } + else + { + console.error("MaxForwards:decrementMaxForwards(): has already reached 0!"); + throw "MaxForwards:decrementMaxForwards(): has already reached 0!"; + } +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ContentType . + * @see gov/nist/javax/sip/header/ContentType.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ContentType(contentType,contentSubtype) { + this.classname="ContentType"; + this.mediaRange=new MediaRange(); + this.headerName=this.NAME; + this.parameters = new NameValueList(); + this.duplicates = new DuplicateNameValueList(); + if(contentType!=null&&contentSubtype!=null) + { + this.setContentType(contentType, contentSubtype); + } +} + +ContentType.prototype = new ParametersHeader(); +ContentType.prototype.constructor=ContentType; +ContentType.prototype.NAME="Content-Type"; +ContentType.prototype.SEMICOLON=";"; + +ContentType.prototype.compareMediaRange =function(media){ + var chaine1=(this.mediaRange.type + "/" + this.mediaRange.subtype).toLowerCase(); + var chaine2=media.toLowerCase(); + var c=0; + var length; + if(chaine1.length>=chaine2.length) + { + length=chaine1.length + } + else + { + length=chaine2.length; + } + for(var i=0;ichaine2.charAt(i)) + { + c=c+1; + } + else if(chaine1.charAt(i) MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; +} + +CallIdentifier.prototype.getLocalId =function(){ + return this.localId; +} + +CallIdentifier.prototype.getHost =function(){ + return this.host; +} + +CallIdentifier.prototype.setLocalId =function(localId){ + this.localId=localId; +} +CallIdentifier.prototype.setCallID =function(cid){ + if (cid == null) + { + console.error("CallIdentifier:setCallID(): cid parameter is null"); + throw "CallIdentifier:setCallID(): cid parameter is null"; + } + var index = cid.indexOf('@'); + if (index == -1) { + this.localId = cid; + this.host = null; + } + else + { + this.localId = cid.substring(0, index); + this.host = cid.substring(index + 1, cid.length); + if (this.localId == null || this.host == null) + { + console.error("CallIdentifier:setCallID(): CallID must be token@token or token"); + throw "CallIdentifier:setCallID(): CallID must be token@token or token"; + + } + } +} + +CallIdentifier.prototype.setHost =function(host){ + this.host=host; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP CallID . + * @see gov/nist/javax/sip/header/CallID.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function CallID() { + this.classname="CallID"; + this.headerName=this.NAME; + this.callIdentifier=new CallIdentifier(); + if(arguments.length!=0) + { + var callId=arguments[0]; + this.callIdentifier = new CallIdentifier(callId); + } +} + +CallID.prototype = new SIPHeader(); +CallID.prototype.constructor=CallID; +CallID.prototype.NAME="Call-ID"; + +CallID.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +CallID.prototype.encodeBodyBuffer =function(buffer){ + if (this.callIdentifier != null) + { + buffer=this.callIdentifier.encodeBuffer(buffer); + } + return buffer; +} + +CallID.prototype.getCallId =function(){ + return this.encodeBody(); +} + +CallID.prototype.getCallIdentifer =function(){ + return this.callIdentifier; +} + +CallID.prototype.setCallId =function(cid){ + this.callIdentifier = new CallIdentifier(cid); +} + +CallID.prototype.setCallIdentifier =function(cid){ + this.callIdentifier=cid; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AuthorizationList . + * @see gov/nist/javax/sip/header/AuthorizationList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function CSeq() { + this.classname="CSeq"; + this.method=null; + this.seqno=null; + if(arguments.length==0) + { + this.headerName=this.CSEQ; + } + else + { + var seqno=arguments[0]; + var method=arguments[1]; + this.headerName=this.CSEQ; + this.seqno = seqno; + var siprequest=new SIPRequest(); + this.method = siprequest.getCannonicalName(method); + } +} + +CSeq.prototype = new SIPHeader(); +CSeq.prototype.constructor=CSeq; +CSeq.prototype.CSEQ="CSeq"; +CSeq.prototype.COLON=":"; +CSeq.prototype.SP=" "; +CSeq.prototype.NEWLINE="\r\n"; + + +CSeq.prototype.encode =function(){ + return this.headerName+this.COLON+this.SP+this.encodeBody()+this.NEWLINE; +} + +CSeq.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +CSeq.prototype.encodeBodyBuffer =function(buffer){ + buffer=buffer+this.seqno+this.SP+this.method.toUpperCase(); + return buffer; +} + +CSeq.prototype.getMethod =function(){ + + return this.method; +} + +CSeq.prototype.setSeqNumber =function(sequenceNumber){ + if (sequenceNumber < 0 ) + { + console.error("CSeq:setSeqNumber(): the sequence number parameter is < 0 : " + sequenceNumber); + throw "CSeq:setSeqNumber(): the sequence number parameter is < 0 : " + sequenceNumber; + } + else if ( sequenceNumber > 2147483647) + { + console.error("CSeq:setSeqNumber(): the sequence number parameter is too large : " + sequenceNumber); + throw "CSeq:setSeqNumber(): the sequence number parameter is too large : " + sequenceNumber; + } + this.seqno = sequenceNumber; +} + +CSeq.prototype.setSequenceNumber =function(sequenceNumber){ + this.setSeqNumber(sequenceNumber); +} + +CSeq.prototype.setMethod =function(method){ + if (method == null) + { + console.error("CSeq:setMethod(): the method parameter is null"); + throw "CSeq:setMethod(): the meth parameter is null"; + } + var siprequest=new SIPRequest(); + this.method = siprequest.getCannonicalName(method); +} + +CSeq.prototype.getSequenceNumber =function(){ + if (this.seqno == null) + { + return 0; + } + else + { + return this.seqno; + } +} +CSeq.prototype.getSeqNumber =function(){ + return this.seqno; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Supported . + * @see gov/nist/javax/sip/header/Supported.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Supported(option_tag) { + this.classname="Supported"; + this.optionTag=null; + if(option_tag!=null) + { + this.headerName="Supported"; + this.optionTag=option_tag; + } + else + { + this.headerName="Supported"; + this.optionTag=null; + } +} + +Supported.prototype = new SIPHeader(); +Supported.prototype.constructor=Supported; +Supported.prototype.COLON=":"; +Supported.prototype.NEWLINE="\r\n"; + +Supported.prototype.encode =function(){ + var retval = this.headerName + this.COLON; + if (this.optionTag != null) + { + retval = retval+this.SP + this.optionTag; + } + retval = retval+this.NEWLINE; + return retval; +} + +Supported.prototype.encodeBody =function(){ + return this.optionTag != null ? this.optionTag : ""; +} + +Supported.prototype.setOptionTag =function(optionTag){ + if (optionTag == null) + { + console.error("Supported:setOptionTag(): the optionTag parameter is null"); + throw "Supported:setOptionTag(): the optionTag parameter is null"; + } + this.optionTag = optionTag; +} + +Supported.prototype.getOptionTag =function(){ + return this.optionTag; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Expires . + * @see gov/nist/javax/sip/header/Expires.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Expires() { + this.classname="Expires"; + this.headerName=this.NAME; + this.expires=null; +} + +Expires.prototype = new SIPHeader(); +Expires.prototype.constructor=Expires; +Expires.prototype.NAME="Expires"; + +Expires.prototype.encodeBody =function(){ + return this.encodeBodyBuffer("").toString(); +} + +Expires.prototype.encodeBodyBuffer =function(buffer){ + buffer=buffer+this.expires; + return buffer; +} + +Expires.prototype.getExpires =function(){ + return this.expires; +} + +Expires.prototype.setExpires =function(expires){ + if (expires < 0) + { + console.error("Expires:setExpires(): bad argument " + expires); + throw "Expires:setExpires(): bad argument " + expires; + } + this.expires = expires; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ContactList . + * @see gov/nist/javax/sip/header/ContactList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ContactList() { + this.classname="ContactList"; + this.headerName = this.NAME; + this.myClass = "Contact"; + this.hlist=new Array(); +} + +ContactList.prototype = new SIPHeaderList(); +ContactList.prototype.constructor=ContactList; +ContactList.prototype.NAME="Contact"; + +ContactList.prototype.clone =function(){ +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ViaList . + * @see gov/nist/javax/sip/header/ViaList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ViaList() { + this.classname="ViaList"; + this.headerName = this.NAME; + this.myClass = "Via"; + this.hlist=new Array(); +} + +ViaList.prototype = new SIPHeaderList(); +ViaList.prototype.constructor=ViaList; +ViaList.prototype.NAME="Via"; + +ViaList.prototype.clone =function(){ +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP WWWAuthenticateList . + * @see gov/nist/javax/sip/header/WWWAuthenticateList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function WWWAuthenticateList() { + this.classname="WWWAuthenticateList"; + this.headerName = this.NAME; + this.myClass = "WWWAuthenticate"; + this.hlist=new Array(); +} + +WWWAuthenticateList.prototype = new SIPHeaderList(); +WWWAuthenticateList.prototype.constructor=WWWAuthenticateList; +WWWAuthenticateList.prototype.NAME="WWW-Authenticate"; + +WWWAuthenticateList.prototype.clone =function(){ +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP RouteList . + * @see gov/nist/javax/sip/header/RouteList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function RouteList() { + this.classname="RouteList"; + this.headerName = this.NAME; + this.myClass = "Route"; + this.hlist=new Array(); +} + +RouteList.prototype = new SIPHeaderList(); +RouteList.prototype.constructor=RouteList; +RouteList.prototype.NAME="Route"; + +RouteList.prototype.clone =function(){ + +} + +RouteList.prototype.encode =function(){ + if (this.hlist.length==0) + { + return ""; + } + else + { + return this.encodeBuffer(""); + } +} +RouteList.prototype.equals =function(){ + +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ProxyAuthenticate . + * @see gov/nist/javax/sip/header/ProxyAuthenticate.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ProxyAuthenticateList() { + this.classname="ProxyAuthenticateList"; + this.headerName = this.NAME; + this.myClass = "ProxyAuthenticate"; + this.hlist=new Array(); +} + +ProxyAuthenticateList.prototype = new SIPHeaderList(); +ProxyAuthenticateList.prototype.constructor=ProxyAuthenticateList; +ProxyAuthenticateList.prototype.NAME="Proxy-Authenticate"; + +ProxyAuthenticateList.prototype.clone =function(){ +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ProxyAuthorizationList . + * @see gov/nist/javax/sip/header/ProxyAuthorizationList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ProxyAuthorizationList() { + this.classname="ProxyAuthorizationList"; + this.headerName = this.NAME; + this.myClass = "ProxyAuthorization"; +} + +ProxyAuthorizationList.prototype = new SIPHeaderList(); +ProxyAuthorizationList.prototype.constructor=ProxyAuthorizationList; +ProxyAuthorizationList.prototype.NAME="Proxy-Authorization"; + +ProxyAuthorizationList.prototype.clone =function(){ + +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AuthorizationList . + * @see gov/nist/javax/sip/header/AuthorizationList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function AuthorizationList() { + this.classname="AuthorizationList"; + this.headerName = this.NAME; + this.myClass = "Authorization"; +} + +AuthorizationList.prototype = new SIPHeaderList(); +AuthorizationList.prototype.constructor=AuthorizationList; +AuthorizationList.prototype.NAME="Authorization"; + +AuthorizationList.prototype.clone =function(){ + +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AllowList . + * @see gov/nist/javax/sip/header/AllowList.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ + +function AllowList() { + this.classname="AllowList"; + this.headerName = this.NAME; + this.myClass = "Allow"; + this.hlist=new Array(); +} + +AllowList.prototype = new SIPHeaderList(); +AllowList.prototype.constructor=AllowList; +AllowList.prototype.NAME="Allow"; + +AllowList.prototype.clone =function(){ + +} + +AllowList.prototype.getMethods =function(){ + var array=new Array(); + for(var i=0;i 255) + { + console.error("HeaderFactoryImpl:createMaxForwardsHeader(): bad maxForwards arg " + maxForwards); + throw "HeaderFactoryImpl:createMaxForwardsHeader(): bad maxForwards arg " + maxForwards; + } + var m = new MaxForwards(); + m.setMaxForwards(maxForwards); + return m; +} + +HeaderFactoryImpl.prototype.createProxyAuthenticateHeader =function(scheme){ + if (scheme == null) + { + console.error("HeaderFactoryImpl:createProxyAuthenticateHeader(): null scheme arg"); + throw "HeaderFactoryImpl:createProxyAuthenticateHeader(): null scheme arg"; + } + var p = new ProxyAuthenticate(); + p.setScheme(scheme); + return p; +} + + +HeaderFactoryImpl.prototype.createProxyAuthorizationHeader =function(scheme){ + if (scheme == null) + { + console.error("HeaderFactoryImpl:createProxyAuthorizationHeader(): null scheme arg"); + throw "HeaderFactoryImpl:createProxyAuthorizationHeader():null scheme arg"; + } + var p = new ProxyAuthorization(); + p.setScheme(scheme); + return p; +} + +HeaderFactoryImpl.prototype.createReasonHeader =function(protocol,cause,text){ + if (protocol == null) + { + console.error("HeaderFactoryImpl:createReasonHeader(): null protocol arg"); + throw "HeaderFactoryImpl:createReasonHeader(): null protocol arg"; + } + if (cause < 0) + { + console.error("HeaderFactoryImpl:createReasonHeader(): bad cause"); + throw "HeaderFactoryImpl:createReasonHeader():bad cause"; + } + var reason = new Reason(); + reason.setProtocol(protocol); + reason.setCause(cause); + reason.setText(text); + return reason; +} + +HeaderFactoryImpl.prototype.createRecordRouteHeader =function(address){ + if ( address == null) + { + console.error("HeaderFactoryImpl:createRecordRouteHeader(): null address arg"); + throw "HeaderFactoryImpl:createRecordRouteHeader(): null address arg"; + } + var recordRoute = new RecordRoute(); + recordRoute.setAddress(address); + return recordRoute; +} + +HeaderFactoryImpl.prototype.createRouteHeader =function(address){ + if (address == null) + { + console.error("HeaderFactoryImpl:createRouteHeader(): null address arg"); + throw "HeaderFactoryImpl:createRouteHeader(): null address arg"; + } + var route = new Route(); + route.setAddress(address); + return route; +} + +HeaderFactoryImpl.prototype.createSubjectHeader =function(subject){ + if (subject == null) + { + console.error("HeaderFactoryImpl:createSubjectHeader(): null subject arg"); + throw "HeaderFactoryImpl:createSubjectHeader(): null subject arg"; + } + var s = new Subject(); + s.setSubject(subject); + return s; +} + +HeaderFactoryImpl.prototype.createSupportedHeader =function(optionTag){ + if (optionTag == null) + { + console.error("HeaderFactoryImpl:createSupportedHeader(): null optionTag arg"); + throw "HeaderFactoryImpl:createSupportedHeader(): null optionTag arg"; + } + var supported = new Supported(); + supported.setOptionTag(optionTag); + return supported; +} + +HeaderFactoryImpl.prototype.createTimeStampHeader =function(timeStamp){ + if (timeStamp < 0) + { + console.error("HeaderFactoryImpl:createTimeStampHeader(): illegal timeStamp"); + throw "HeaderFactoryImpl:createTimeStampHeader(): illegal timeStamp"; + } + var t = new TimeStamp(); + t.setTimeStamp(timeStamp); + return t; +} + +HeaderFactoryImpl.prototype.createToHeader =function(address,tag){ + if (address == null) + { + console.error("HeaderFactoryImpl:createSupportedHeader(): null address arg"); + throw "HeaderFactoryImpl:createSupportedHeader(): null address arg"; + } + var to = new To(); + to.setAddress(address); + if (tag != null) + { + to.setTag(tag); + } + return to; +} + +HeaderFactoryImpl.prototype.createUserAgentHeader =function(product){ + if (product == null) + { + console.error("HeaderFactoryImpl:createUserAgentHeader(): null product arg"); + throw "HeaderFactoryImpl:createUserAgentHeader(): null product arg"; + } + var userAgent = new UserAgent(); + userAgent.setProduct(product); + return userAgent; +} + +HeaderFactoryImpl.prototype.createViaHeader =function(host,port,transport,branch){ + if (host == null || transport == null) + { + console.error("HeaderFactoryImpl:createViaHeader(): null host or transport arg"); + throw "HeaderFactoryImpl:createViaHeader(): null host or transport arg"; + } + var via = new Via(); + if (branch != null) + { + via.setBranch(branch); + } + if(host.indexOf(':') >= 0&& host.indexOf('[') < 0) + { + if(this.stripAddressScopeZones) + { + var zoneStart = host.indexOf('%'); + if(zoneStart != -1) + { + host = host.substring(0, zoneStart); + } + } + host = "[" + host + "]"; + } + via.setHost(host); + if(port != null) + { + via.setPort(port); + } + if(transport != null) + { + via.setTransport(transport); + } + else + { + via.setTransport("WS"); + } + return via; +} + + +HeaderFactoryImpl.prototype.createWWWAuthenticateHeader =function(scheme){ + if (scheme == null) + { + console.error("HeaderFactoryImpl:createWWWAuthenticateHeader(): null scheme arg"); + throw "HeaderFactoryImpl:createWWWAuthenticateHeader(): null scheme arg"; + } + var www = new WWWAuthenticate(); + www.setScheme(scheme); + return www; +} + +HeaderFactoryImpl.prototype.createHeader =function(){ + if(arguments.length==1) + { + var headerText = arguments[0]; + var smp = new StringMsgParser(); + var sipHeader = smp.parseSIPHeader(headerText.trim()); + + if(sipHeader instanceof SIPHeaderList) + { + if (sipHeader.size() > 1) + { + console.error("HeaderFactoryImpl:createHeader(): only singleton allowed " + headerText); + throw "HeaderFactoryImpl:createHeader():only singleton allowed " + headerText; + } + else if (sipHeader.size() == 0) + { + var classname= sipHeader.getMyClass(); + var header=new Function('return new ' + classname)(); + return header; + } + else + { + return sipHeader.getFirst(); + } + } + else + { + return sipHeader; + } + } + else if(arguments.length==2) + { + var headerName = arguments[0]; + var headerValue = arguments[1]; + if (headerName == null) + { + console.error("header name is null"); + console.error("HeaderFactoryImpl:createHeader(): header name is null"); + throw "HeaderFactoryImpl:createHeader(): header name is null"; + } + var hdrText =headerName+":"+headerValue; + return this.createHeader(hdrText); + } +} + + +HeaderFactoryImpl.prototype.createHeaders =function(headers){ + if (headers == null) + { + console.error("HeaderFactoryImpl:createHeaders(): null headers arg"); + throw "HeaderFactoryImpl:createHeaders(): null headers arg"; + } + var smp = new StringMsgParser(); + var shdr = smp.parseSIPHeader(headers); + if (shdr instanceof SIPHeaderList) + { + return shdr; + } + else + { + console.error("HeaderFactoryImpl:createHeaders():List of headers of this type is not allowed in a message", 0); + throw "HeaderFactoryImpl:createHeaders(): list of headers of this type is not allowed in a message"; + } +} + +HeaderFactoryImpl.prototype.createRouteList =function(recordRouteList){ + if(recordRouteList!=null) + { + var routeList = new RouteList(); + for(var i=recordRouteList.getHeaderList().length-1;i>=0;i--) + { + var rr = recordRouteList.getHeaderList()[i]; + var route = new Route(); + var address = rr.getAddress(); + + route.setAddress(address); + route.setParameters(rr.getParameters()); + routeList.add(route); + } + return routeList; + } + else + { + return new RouteList(); + } +} + + +HeaderFactoryImpl.prototype.createRequestLine =function(requestLine){ + var requestLineParser = new RequestLineParser(requestLine); + return requestLineParser.parse(); +} + + +HeaderFactoryImpl.prototype.createStatusLine =function(statusLine){ + var statusLineParser = new StatusLineParser(statusLine); + return statusLineParser.parse(); +} + +HeaderFactoryImpl.prototype.createAuthorizationHeader =function(){ + if(arguments.length==1) + { + var scheme=arguments[0]; + return this.createAuthorizationHeaderargu1(scheme); + } + else + { + var response=arguments[0]; + var request=arguments[1]; + var sipPassword=arguments[2]; + var sipLogin=arguments[3]; + var sipDomainUri=arguments[4]; + return this.createAuthorizationHeaderargu2(response, request, sipPassword, sipLogin); + } +} + +HeaderFactoryImpl.prototype.createAuthorizationHeaderargu1 =function(scheme){ + if (scheme == null) + { + console.error("HeaderFactoryImpl:createAuthorizationHeaderargu1(): null scheme arg"); + throw "HeaderFactoryImpl:createAuthorizationHeaderargu1(): null scheme arg"; + } + var auth = new Authorization(); + auth.setScheme(scheme); + return auth; +} + + +HeaderFactoryImpl.prototype.createAuthorizationHeaderargu2 =function(response,request,sipPassword,sipLogin){ + if(response.hasHeader("www-authenticate")) + { + var realm=response.getWWWAuthenticate().getRealm(); + var scheme=response.getWWWAuthenticate().getScheme(); + var nonce=response.getWWWAuthenticate().getNonce(); + var qop=response.getWWWAuthenticate().getQop(); + var authorization=new Authorization(); + } + else if(response.hasHeader("proxy-authenticate")) + { + realm=response.getProxyAuthenticate().getRealm(); + scheme=response.getProxyAuthenticate().getScheme(); + nonce=response.getProxyAuthenticate().getNonce(); + qop=response.getProxyAuthenticate().getQop(); + var proxyauthorization=new ProxyAuthorization(); + } + var mda=new MessageDigestAlgorithm(); + var method=response.getCSeq().getMethod(); + var cnonce=null; + var nc=null; + if(qop!=null) + { + var cnonce=Math.floor(Math.random()*16777215).toString(16); + var nc="00000001"; + } + var resp=mda.calculateResponse(sipLogin,realm,sipPassword,nonce,nc,cnonce,method,request.getRequestURI().toString(),null,qop); + + if(response.hasHeader("www-authenticate")) + { + authorization.setUsername(sipLogin); + authorization.setRealm(realm); + authorization.setNonce(nonce); + if(cnonce!=null) authorization.setCNonce(cnonce); + if(nc!=null) authorization.setNonceCount(nc); + authorization.setScheme(scheme); + authorization.setResponse(resp); + authorization.setURI(request.getRequestURI()); + authorization.setAlgorithm("MD5"); + if(qop!=null) authorization.setQop(qop); + return authorization; + } + else if(response.hasHeader("proxy-authenticate")) + { + proxyauthorization.setUsername(sipLogin); + proxyauthorization.setRealm(realm); + proxyauthorization.setNonce(nonce); + if(cnonce!=null) proxyauthorization.setCNonce(cnonce); + if(nc!=null) proxyauthorization.setNonceCount(nc); + proxyauthorization.setScheme(scheme); + proxyauthorization.setResponse(resp); + proxyauthorization.setURI(request.getRequestURI()); + proxyauthorization.setAlgorithm("MD5"); + if(qop!=null) proxyauthorization.setQop(qop); + return proxyauthorization; + } +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Parser . + * @see gov/nist/javax/sip/parser/Parser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function Parser() { + this.classname="Parser"; +} + +Parser.prototype = new ParserCore(); +Parser.prototype.constructor=Parser; +Parser.prototype.INVITE=LexerCore.prototype.START+5; +Parser.prototype.ACK=LexerCore.prototype.START+6; +Parser.prototype.OPTIONS=LexerCore.prototype.START+8; +Parser.prototype.BYE=LexerCore.prototype.START+7; +Parser.prototype.REGISTER=LexerCore.prototype.START+4; +Parser.prototype.CANCEL=LexerCore.prototype.START+9; +Parser.prototype.SUBSCRIBE=LexerCore.prototype.START+53; +Parser.prototype.NOTIFY=LexerCore.prototype.START+54; +Parser.prototype.PUBLISH=LexerCore.prototype.START+67; +Parser.prototype.MESSAGE=LexerCore.prototype.START+70; +Parser.prototype.ID=LexerCore.prototype.ID; +Parser.prototype.SIP=LexerCore.prototype.START+3; + +Parser.prototype.createParseException =function(){ +} + +Parser.prototype.getLexer =function(){ + return this.lexer; +} + +Parser.prototype.sipVersion =function(){ + var tok = this.lexer.match(this.SIP); + if (tok.getTokenValue().toUpperCase()!="SIP") { + this.createParseException("Expecting SIP"); + } + this.lexer.match('/'); + tok = this.lexer.match(this.ID); + if (tok.getTokenValue()!="2.0") { + this.createParseException("Expecting SIP/2.0"); + } + return "SIP/2.0"; + +} +Parser.prototype.method =function(){ + var tokens = this.lexer.peekNextToken(1); + var token = tokens[0]; + if (token.getTokenType() == this.INVITE + || token.getTokenType() == this.ACK + || token.getTokenType() == this.OPTIONS + || token.getTokenType() == this.BYE + || token.getTokenType() == this.REGISTER + || token.getTokenType() == this.CANCEL + || token.getTokenType() == this.SUBSCRIBE + || token.getTokenType() == this.NOTIFY + || token.getTokenType() == this.PUBLISH + || token.getTokenType() == this.MESSAGE + || token.getTokenType() == this.ID) { + this.lexer.consume(); + return token.getTokenValue(); + } else { + console.error("Parser:method(): invalid Method"); + throw "Parser:method(): invalid Method"; + } + +} +Parser.prototype.checkToken =function(token){ + if (token == null || token.length == 0) { + console.error("Parser:checkToken(): null or empty token"); + throw "Parser:method(): null or empty token"; + } else { + for (var i = 0; i < token.length; ++i) { + var lc=new LexerCore(); + if (!lc.isTokenChar(token.charAt(i))) { + console.error("Parser:checkToken(): invalid character(s) in string (not allowed in 'token')",i); + throw "Parser:method(): invalid character(s) in string (not allowed in 'token')"; + } + } + } +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Lexer . + * @see gov/nist/javax/sip/parser/HeaderParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function Lexer(lexerName, buffer) { + this.classname="Lexer"; + this.buffer = buffer; + this.bufferLen = buffer.length; + this.ptr = 0; + this.currentLexer = new Array(); + this.currentLexerName = lexerName; + this.selectLexer(lexerName); +} + +Lexer.prototype = new LexerCore(); +Lexer.prototype.constructor=Lexer; +Lexer.prototype.ErrorInfoHeader="Error-Info"; +Lexer.prototype.AllowEventsHeader="Allow-Events"; +Lexer.prototype.AuthenticationInfoHeader="Authentication-Info"; +Lexer.prototype.EventHeader="Event"; +Lexer.prototype.MinExpiresHeader="Min-Expires"; +Lexer.prototype.RSeqHeader="RSeq"; +Lexer.prototype.RAckHeader="RAck"; +Lexer.prototype.ReasonHeader="Reason"; +Lexer.prototype.ReplyToHeader="Reply-To"; +Lexer.prototype.SubscriptionStateHeader="Subscription-State"; +Lexer.prototype.TimeStampHeader="Timestamp"; +Lexer.prototype.InReplyToHeader="In-Reply-To"; +Lexer.prototype.MimeVersionHeader="MIME-Version"; +Lexer.prototype.AlertInfoHeader="Alert-Info"; +Lexer.prototype.FromHeader="From"; +Lexer.prototype.ToHeader="To"; +Lexer.prototype.ReferToHeader="Refer-To"; +Lexer.prototype.ViaHeader="Via"; +Lexer.prototype.UserAgentHeader="User-Agent"; +Lexer.prototype.ServerHeader="Server"; +Lexer.prototype.AcceptEncodingHeader="Accept-Encoding"; +Lexer.prototype.AcceptHeader="Accept"; +Lexer.prototype.AllowHeader="Allow"; +Lexer.prototype.RouteHeader="Route"; +Lexer.prototype.AuthorizationHeader="Authorization"; +Lexer.prototype.ProxyAuthorizationHeader="Proxy-Authorization"; +Lexer.prototype.RetryAfterHeader="Retry-After"; +Lexer.prototype.ProxyRequireHeader="Proxy-Require"; +Lexer.prototype.ContentLanguageHeader="Content-Language"; +Lexer.prototype.UnsupportedHeader="Unsupported"; +Lexer.prototype.SupportedHeader="Supported"; +Lexer.prototype.WarningHeader="Warning"; +Lexer.prototype.MaxForwardsHeader="Max-Forwards"; +Lexer.prototype.DateHeader="Date"; +Lexer.prototype.PriorityHeader="Priority"; +Lexer.prototype.ProxyAuthenticateHeader="Proxy-Authenticate"; +Lexer.prototype.ContentEncodingHeader="Content-Encoding"; +Lexer.prototype.ContentLengthHeader="Content-Length"; +Lexer.prototype.SubjectHeader="Subject"; +Lexer.prototype.ContentTypeHeader="Content-Type"; +Lexer.prototype.ContactHeader="Contact"; +Lexer.prototype.CallIdHeader="Call-ID"; +Lexer.prototype.RequireHeader="Require"; +Lexer.prototype.ExpiresHeader="Expires"; +Lexer.prototype.RecordRouteHeader="Record-Route"; +Lexer.prototype.OrganizationHeader="Organization"; +Lexer.prototype.CSeqHeader="CSeq"; +Lexer.prototype.AcceptLanguageHeader="Accept-Language"; +Lexer.prototype.WWWAuthenticateHeader="WWW-Authenticate"; +Lexer.prototype.CallInfoHeader="Call-Info"; +Lexer.prototype.ContentDispositionHeader="Content-Disposition"; +Lexer.prototype.SIPETagHeader="SIP-ETag"; +Lexer.prototype.SIPIfMatchHeader="SIP-If-Match"; +Lexer.prototype.SessionExpiresHeader="Session-Expires"; +Lexer.prototype.MinSEHeader="Min-SE"; +Lexer.prototype.ReferredByHeader="Referred-By"; +Lexer.prototype.ReplacesHeader="Replaces"; +Lexer.prototype.JoinHeader="Join"; +Lexer.prototype.PathHeader="Path"; +Lexer.prototype.ServiceRouteHeader="Service-Route"; +Lexer.prototype.PAssertedIdentityHeader="P-Asserted-Identity"; +Lexer.prototype.PPreferredIdentityHeader="P-Preferred-Identity"; +Lexer.prototype.PrivacyHeader="Privacy"; +Lexer.prototype.PCalledPartyIDHeader="P-Called-Party-ID"; +Lexer.prototype.PAssociatedURIHeader="P-Associated-URI"; +Lexer.prototype.PVisitedNetworkIDHeader="P-Visited-Network-ID"; +Lexer.prototype.PChargingFunctionAddressesHeader="P-Charging-Function-Addresses"; +Lexer.prototype.PChargingVectorHeader="P-Charging-Vector"; +Lexer.prototype.PAccessNetworkInfoHeader="P-Access-Network-Info"; +Lexer.prototype.PMediaAuthorizationHeader="P-Media-Authorization"; +Lexer.prototype.SecurityServerHeader="Security-Server"; +Lexer.prototype.SecurityVerifyHeader="Security-Verify"; +Lexer.prototype.SecurityClientHeader="Security-Client"; +Lexer.prototype.PUserDatabaseHeader="P-User-Database"; +Lexer.prototype.PProfileKeyHeader="P-Profile-Key"; +Lexer.prototype.PServedUserHeader="P-Served-User"; +Lexer.prototype.PPreferredServiceHeader="P-Preferred-Service"; +Lexer.prototype.PAssertedServiceHeader="P-Asserted-Service"; +Lexer.prototype.ReferencesHeader="References"; +Lexer.prototype.AcceptContact="Accept-Contact"; + + +Lexer.prototype.getHeaderName =function(line){ + if (line == null) + { + return null; + } + var headerName = null; + try { + var begin = line.indexOf(":"); + headerName = null; + if (begin >= 1) + { + headerName = line.substring(0, begin).trim(); + } + } catch (ex) { + console.error("Lexer:getHeaderName(): catched exception:"+ex); + return null; + } + return headerName; +} +Lexer.prototype.getHeaderValue =function(line){ + if (line == null) + { + return null; + } + var headerValue = null; + try { + var begin = line.indexOf(":"); + headerValue = line.substring(begin + 1); + } catch (ex) { + console.error("Lexer:getHeaderValue(): catched exception:"+ex); + return null; + } + return headerValue; +} +Lexer.prototype.selectLexer =function(lexerName){ + //in javascript, we can not realize thread, so i ignore the key work synchronized in the function + var n=null; + for(var i=0;i 31) { + console.error("HeaderParser:date(): bad day"); + throw "HeaderParser:date(): bad day"; + } + retval.set(5, day); + this.lexer.match(' '); + var month = this.lexer.ttoken().toLowerCase(); + if (month.equals("jan")) { + retval.setMonth( 0);//Calendar.MONTH=2 + } else if (month=="feb") { + retval.setMonth(1); + } else if (month=="mar") { + retval.setMonth(2); + } else if (month=="apr") { + retval.setMonth(3); + } else if (month=="may") { + retval.setMonth(4); + } else if (month=="jun") { + retval.setMonth(5); + } else if (month=="jul") { + retval.setMonth(6); + } else if (month=="aug") { + retval.setMonth(7); + } else if (month=="sep") { + retval.setMonth(8); + } else if (month=="oct") { + retval.setMonth(9); + } else if (month=="nov") { + retval.setMonth(10); + } else if (month=="dec") { + retval.setMonth(11); + } + this.lexer.match(' '); + var s2 = this.lexer.number(); + var yr = s2; + retval.setYear(yr); + return retval; + } catch (ex) { + console.error("HeaderParser:date(): bad date field"); + throw "HeaderParser:date(): bad date field"; + } +} + +HeaderParser.prototype.time =function(calendar){ + try { + var s = this.lexer.number(); + var hour = s; + calendar.setHours(hour); + this.lexer.match(':'); + s = this.lexer.number(); + var min = s; + calendar.setMinutes(min); + this.lexer.match(':'); + s = this.lexer.number(); + var sec = s; + calendar.setSeconds(sec); + } catch (ex) { + console.error("HeaderParser:time(): error processing time "); + throw "HeaderParser:time():error processing time "; + } +} + + +HeaderParser.prototype.parse =function(){ + var name = this.lexer.getNextToken(':'); + this.lexer.consume(1); + var body = this.lexer.getLine().replace(/^(\s)+|(\s)+$/g, ''); + var retval = new ExtensionHeaderImpl(name); + retval.setValue(body); + return retval; +} +HeaderParser.prototype.headerName =function(tok){ + this.lexer.match(tok); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ParametersParser . + * @see gov/nist/javax/sip/parser/ParametersParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ParametersParser() { + this.classname="ParametersParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var buffer=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", buffer); + } +} + +ParametersParser.prototype = new HeaderParser(); +ParametersParser.prototype.constructor=ParametersParser; + +ParametersParser.prototype.parse =function(parametersHeader){ + this.lexer.SPorHT(); + while (this.lexer.lookAhead(0) == ';') { + this.lexer.consume(1); + this.lexer.SPorHT(); + var nv = this.nameValue(); + parametersHeader.setParameter_nv(nv); + this.lexer.SPorHT(); + } +} +ParametersParser.prototype.parseNameValueList =function(parametersHeader){ + parametersHeader.removeParameters(); + while (true) { + this.lexer.SPorHT(); + var nv = nameValue(); + + parametersHeader.setParameter(nv.getName(), nv.getValueAsObject()); + this.lexer.SPorHT(); + if (this.lexer.lookAhead(0) != ';') { + break; + } + else { + this.lexer.consume(1); + } + } +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP TokenTypes . + * @see gov/nist/javax/sip/parser/TokenTypes.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function TokenTypes() { + this.classname="TokenTypes"; +} + +TokenTypes.prototype.constructor=Parser; +TokenTypes.prototype.START = LexerCore.prototype.START; +// Everything under this is reserved +TokenTypes.prototype.END = LexerCore.prototype.END; +// End markder. + +TokenTypes.prototype.SIP = LexerCore.prototype.START + 3; +TokenTypes.prototype.REGISTER = LexerCore.prototype.START + 4; +TokenTypes.prototype.INVITE = LexerCore.prototype.START + 5; +TokenTypes.prototype.ACK = LexerCore.prototype.START + 6; +TokenTypes.prototype.BYE = LexerCore.prototype.START + 7; +TokenTypes.prototype.OPTIONS = LexerCore.prototype.START + 8; +TokenTypes.prototype.CANCEL = LexerCore.prototype.START + 9; +TokenTypes.prototype.ERROR_INFO = LexerCore.prototype.START + 10; +TokenTypes.prototype.IN_REPLY_TO = LexerCore.prototype.START + 11; +TokenTypes.prototype.MIME_VERSION = LexerCore.prototype.START + 12; +TokenTypes.prototype.ALERT_INFO = LexerCore.prototype.START + 13; +TokenTypes.prototype.FROM = LexerCore.prototype.START + 14; +TokenTypes.prototype.TO = LexerCore.prototype.START + 15; +TokenTypes.prototype.VIA = LexerCore.prototype.START + 16; +TokenTypes.prototype.USER_AGENT = LexerCore.prototype.START + 17; +TokenTypes.prototype.SERVER = LexerCore.prototype.START + 18; +TokenTypes.prototype.ACCEPT_ENCODING = LexerCore.prototype.START + 19; +TokenTypes.prototype.ACCEPT = LexerCore.prototype.START + 20; +TokenTypes.prototype.ALLOW = LexerCore.prototype.START + 21; +TokenTypes.prototype.ROUTE = LexerCore.prototype.START + 22; +TokenTypes.prototype.AUTHORIZATION = LexerCore.prototype.START + 23; +TokenTypes.prototype.PROXY_AUTHORIZATION = LexerCore.prototype.START + 24; +TokenTypes.prototype.RETRY_AFTER = LexerCore.prototype.START + 25; +TokenTypes.prototype.PROXY_REQUIRE = LexerCore.prototype.START + 26; +TokenTypes.prototype.CONTENT_LANGUAGE = LexerCore.prototype.START + 27; +TokenTypes.prototype.UNSUPPORTED = LexerCore.prototype.START + 28; +TokenTypes.prototype.SUPPORTED = LexerCore.prototype.START + 20; +TokenTypes.prototype.WARNING = LexerCore.prototype.START + 30; +TokenTypes.prototype.MAX_FORWARDS = LexerCore.prototype.START + 31; +TokenTypes.prototype.DATE = LexerCore.prototype.START + 32; +TokenTypes.prototype.PRIORITY = LexerCore.prototype.START + 33; +TokenTypes.prototype.PROXY_AUTHENTICATE = LexerCore.prototype.START + 34; +TokenTypes.prototype.CONTENT_ENCODING = LexerCore.prototype.START + 35; +TokenTypes.prototype.CONTENT_LENGTH = LexerCore.prototype.START + 36; +TokenTypes.prototype.SUBJECT = LexerCore.prototype.START + 37; +TokenTypes.prototype.CONTENT_TYPE = LexerCore.prototype.START + 38; +TokenTypes.prototype.CONTACT = LexerCore.prototype.START + 39; +TokenTypes.prototype.CALL_ID = LexerCore.prototype.START + 40; +TokenTypes.prototype.REQUIRE = LexerCore.prototype.START + 41; +TokenTypes.prototype.EXPIRES = LexerCore.prototype.START + 42; +TokenTypes.prototype.ENCRYPTION = LexerCore.prototype.START + 43; +TokenTypes.prototype.RECORD_ROUTE = LexerCore.prototype.START + 44; +TokenTypes.prototype.ORGANIZATION = LexerCore.prototype.START + 45; +TokenTypes.prototype.CSEQ = LexerCore.prototype.START + 46; +TokenTypes.prototype.ACCEPT_LANGUAGE = LexerCore.prototype.START + 47; +TokenTypes.prototype.WWW_AUTHENTICATE = LexerCore.prototype.START + 48; +TokenTypes.prototype.RESPONSE_KEY = LexerCore.prototype.START + 49; +TokenTypes.prototype.HIDE = LexerCore.prototype.START + 50; +TokenTypes.prototype.CALL_INFO = LexerCore.prototype.START + 51; +TokenTypes.prototype.CONTENT_DISPOSITION = LexerCore.prototype.START + 52; +TokenTypes.prototype.SUBSCRIBE = LexerCore.prototype.START + 53; +TokenTypes.prototype.NOTIFY = LexerCore.prototype.START + 54; +TokenTypes.prototype.TIMESTAMP = LexerCore.prototype.START + 55; +TokenTypes.prototype.SUBSCRIPTION_STATE = LexerCore.prototype.START + 56; +TokenTypes.prototype.TEL = LexerCore.prototype.START + 57; +TokenTypes.prototype.REPLY_TO = LexerCore.prototype.START + 58; +TokenTypes.prototype.REASON = LexerCore.prototype.START + 59; +TokenTypes.prototype.RSEQ = LexerCore.prototype.START + 60; +TokenTypes.prototype.RACK = LexerCore.prototype.START + 61; +TokenTypes.prototype.MIN_EXPIRES = LexerCore.prototype.START + 62; +TokenTypes.prototype.EVENT = LexerCore.prototype.START + 63; +TokenTypes.prototype.AUTHENTICATION_INFO = LexerCore.prototype.START + 64; +TokenTypes.prototype.ALLOW_EVENTS = LexerCore.prototype.START + 65; +TokenTypes.prototype.REFER_TO = LexerCore.prototype.START + 66; + +// JvB: added to support RFC3903 +TokenTypes.prototype.PUBLISH = LexerCore.prototype.START + 67; +TokenTypes.prototype.SIP_ETAG = LexerCore.prototype.START + 68; +TokenTypes.prototype.SIP_IF_MATCH = LexerCore.prototype.START + 69; + + + + +TokenTypes.prototype.MESSAGE = LexerCore.prototype.START + 70; + +// IMS Headers +TokenTypes.prototype.PATH = LexerCore.prototype.START + 71; +TokenTypes.prototype.SERVICE_ROUTE = LexerCore.prototype.START + 72; +TokenTypes.prototype.P_ASSERTED_IDENTITY = LexerCore.prototype.START + 73; +TokenTypes.prototype.P_PREFERRED_IDENTITY = LexerCore.prototype.START + 74; +TokenTypes.prototype.P_VISITED_NETWORK_ID = LexerCore.prototype.START + 75; +TokenTypes.prototype.P_CHARGING_FUNCTION_ADDRESSES = LexerCore.prototype.START + 76; +TokenTypes.prototype.P_VECTOR_CHARGING = LexerCore.prototype.START + 77; + + + +// issued by Miguel Freitas - IMS headers +TokenTypes.prototype.PRIVACY = LexerCore.prototype.START + 78; +TokenTypes.prototype.P_ACCESS_NETWORK_INFO = LexerCore.prototype.START + 79; +TokenTypes.prototype.P_CALLED_PARTY_ID = LexerCore.prototype.START + 80; +TokenTypes.prototype.P_ASSOCIATED_URI = LexerCore.prototype.START + 81; +TokenTypes.prototype.P_MEDIA_AUTHORIZATION = LexerCore.prototype.START + 82; +TokenTypes.prototype.P_MEDIA_AUTHORIZATION_TOKEN = LexerCore.prototype.START + 83; + + +// pmusgrave - additions +TokenTypes.prototype.REFERREDBY_TO = LexerCore.prototype.START + 84; + +// pmusgrave RFC4028 +TokenTypes.prototype.SESSIONEXPIRES_TO = LexerCore.prototype.START + 85; +TokenTypes.prototype.MINSE_TO = LexerCore.prototype.START + 86; + +// pmusgrave RFC3891 +TokenTypes.prototype.REPLACES_TO = LexerCore.prototype.START + 87; + +// pmusgrave sips bug fix +TokenTypes.prototype.SIPS = LexerCore.prototype.START + 88; + + +// issued by Miguel Freitas - SIP Security Agreement (RFC3329) +TokenTypes.prototype.SECURITY_SERVER = LexerCore.prototype.START + 89; +TokenTypes.prototype.SECURITY_CLIENT = LexerCore.prototype.START + 90; +TokenTypes.prototype.SECURITY_VERIFY = LexerCore.prototype.START + 91; + +// jean deruelle RFC3911 +TokenTypes.prototype.JOIN_TO = LexerCore.prototype.START + 92; + +// aayush.bhatnagar: RFC 4457 support. +TokenTypes.prototype.P_USER_DATABASE = LexerCore.prototype.START + 93; +//aayush.bhatnagar: RFC 5002 support. +TokenTypes.prototype.P_PROFILE_KEY = LexerCore.prototype.START + 94; +//aayush.bhatnagar: RFC 5502 support. +TokenTypes.prototype.P_SERVED_USER = LexerCore.prototype.START + 95; +//aayush.bhatnaagr: P-Preferred-Service Header: +TokenTypes.prototype.P_PREFERRED_SERVICE = LexerCore.prototype.START + 96; +//aayush.bhatnagar: P-Asserted-Service Header: +TokenTypes.prototype.P_ASSERTED_SERVICE = LexerCore.prototype.START + 97; +//mranga - References header +TokenTypes.prototype.REFERENCES = LexerCore.prototype.START + 98; + +TokenTypes.prototype.ACCEPT_CONTACT = LexerCore.prototype.START + 99; + +TokenTypes.prototype.ALPHA = LexerCore.prototype.ALPHA; +TokenTypes.prototype.DIGIT = LexerCore.prototype.DIGIT; +TokenTypes.prototype.ID = LexerCore.prototype.ID; +TokenTypes.prototype.WHITESPACE = LexerCore.prototype.WHITESPACE; +TokenTypes.prototype.BACKSLASH = LexerCore.prototype.BACKSLASH; +TokenTypes.prototype.QUOTE = LexerCore.prototype.QUOTE; +TokenTypes.prototype.AT = LexerCore.prototype.AT; +TokenTypes.prototype.SP = LexerCore.prototype.SP; +TokenTypes.prototype.HT = LexerCore.prototype.HT; +TokenTypes.prototype.COLON = LexerCore.prototype.COLON; +TokenTypes.prototype.STAR = LexerCore.prototype.STAR; +TokenTypes.prototype.DOLLAR = LexerCore.prototype.DOLLAR; +TokenTypes.prototype.PLUS = LexerCore.prototype.PLUS; +TokenTypes.prototype.POUND = LexerCore.prototype.POUND; +TokenTypes.prototype.MINUS = LexerCore.prototype.MINUS; +TokenTypes.prototype.DOUBLEQUOTE = LexerCore.prototype.DOUBLEQUOTE; +TokenTypes.prototype.TILDE = LexerCore.prototype.TILDE; +TokenTypes.prototype.BACK_QUOTE = LexerCore.prototype.BACK_QUOTE; +TokenTypes.prototype.NULL = LexerCore.prototype.NULL; +TokenTypes.prototype.EQUALS = '='.charCodeAt(0); +TokenTypes.prototype.SEMICOLON = ';'.charCodeAt(0); +TokenTypes.prototype.SLASH = '/'.charCodeAt(0); +TokenTypes.prototype.L_SQUARE_BRACKET = '['.charCodeAt(0); +TokenTypes.prototype.R_SQUARE_BRACKET = ']'.charCodeAt(0); +TokenTypes.prototype.R_CURLY = '}'.charCodeAt(0); +TokenTypes.prototype.L_CURLY = '{'.charCodeAt(0); +TokenTypes.prototype.HAT = '^'.charCodeAt(0); +TokenTypes.prototype.BAR = '|'.charCodeAt(0); +TokenTypes.prototype.DOT = '.'.charCodeAt(0); +TokenTypes.prototype.EXCLAMATION = '!'.charCodeAt(0); +TokenTypes.prototype.LPAREN = '('.charCodeAt(0); +TokenTypes.prototype.RPAREN = ')'.charCodeAt(0); +TokenTypes.prototype.GREATER_THAN = '>'.charCodeAt(0); +TokenTypes.prototype.LESS_THAN = '<'.charCodeAt(0); +TokenTypes.prototype.PERCENT = '%'.charCodeAt(0); +TokenTypes.prototype.QUESTION = '?'.charCodeAt(0); +TokenTypes.prototype.AND = '&'.charCodeAt(0); +TokenTypes.prototype.UNDERSCORE = '_'.charCodeAt(0);/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP TokenNames . + * @see gov/nist/javax/sip/parser/TokenNames.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function TokenNames() { + this.classname="TokenNames"; +} + +TokenNames.prototype.INVITE = "INVITE"; +TokenNames.prototype.ACK = "ACK"; +TokenNames.prototype.BYE = "BYE"; +TokenNames.prototype.SUBSCRIBE = "SUBSCRIBE"; +TokenNames.prototype.NOTIFY = "NOTIFY"; +TokenNames.prototype.OPTIONS = "OPTIONS"; +TokenNames.prototype.REGISTER = "REGISTER"; +TokenNames.prototype.MESSAGE = "MESSAGE"; +TokenNames.prototype.PUBLISH = "PUBLISH"; + +TokenNames.prototype.SIP = "sip"; +TokenNames.prototype.SIPS = "sips"; +TokenNames.prototype.TEL = "tel"; +TokenNames.prototype.GMT = "GMT"; +TokenNames.prototype.MON = "Mon"; +TokenNames.prototype.TUE = "Tue"; +TokenNames.prototype.WED = "Wed"; +TokenNames.prototype.THU = "Thu"; +TokenNames.prototype.FRI = "Fri"; +TokenNames.prototype.SAT = "Sat"; +TokenNames.prototype.SUN = "Sun"; +TokenNames.prototype.JAN = "Jan"; +TokenNames.prototype.FEB = "Feb"; +TokenNames.prototype.MAR = "Mar"; +TokenNames.prototype.APR = "Apr"; +TokenNames.prototype.MAY = "May"; +TokenNames.prototype.JUN = "Jun"; +TokenNames.prototype.JUL = "Jul"; +TokenNames.prototype.AUG = "Aug"; +TokenNames.prototype.SEP = "Sep"; +TokenNames.prototype.OCT = "Oct"; +TokenNames.prototype.NOV = "Nov"; +TokenNames.prototype.DEC = "Dec"; +TokenNames.prototype.K = "K"; +TokenNames.prototype.C = "C"; +TokenNames.prototype.E = "E"; +TokenNames.prototype.F = "F"; +TokenNames.prototype.I = "I"; +TokenNames.prototype.M = "M"; +TokenNames.prototype.L = "L"; +TokenNames.prototype.S = "S"; +TokenNames.prototype.T = "T"; +TokenNames.prototype.U = "U";// JvB: added +TokenNames.prototype.V = "V"; +TokenNames.prototype.R = "R"; +TokenNames.prototype.O = "O"; +TokenNames.prototype.X = "X"; //Jozef Saniga added +TokenNames.prototype.B = "B"; + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP StringMsgParser . + * @see gov/nist/javax/sip/parser/StringMsgParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function StringMsgParser(exhandler) { + this.classname="StringMsgParser"; + this.readBody=true; + this.parseExceptionListener=null; + this.rawStringMessage=null; + this.strict=true; + this.computeContentLengthFromMessage = false; + this.viaCount=0; + if(exhandler!=null) + { + this.parseExceptionListener = exhandler; + } +} + +StringMsgParser.prototype.SIP_VERSION_STRING="SIP/2.0"; + +StringMsgParser.prototype.setParseExceptionListener =function(pexhandler){ + this.parseExceptionListener = pexhandler; +} + +StringMsgParser.prototype.parseSIPMessage =function(){ + if(typeof arguments[0]=="string") + { + var msgString=arguments[0]; + return this.parseSIPMessagestring(msgString); + } + else if(typeof arguments[0]=="object") + { + var msgBuffer=arguments[0]; + return this.parseSIPMessagebyte(msgBuffer); + } +} + +StringMsgParser.prototype.parseSIPMessagestring =function(msgString){ + if (msgString == null || msgString.length == 0) { + return null; + } + this.rawStringMessage = msgString; + var i = 0; + try { + while (msgString.charCodeAt(i) < 0x20) { + i++; + } + } catch (ex) { + console.error("StringMsgParser:parseSIPMessagestring(): catched exception:"+ex); + return null; + } + var currentLine = null; + var currentHeader = null; + var isFirstLine = true; + var message = null; + do { + var lineStart = i; + try { + var c = msgString.charAt(i); + while (c != '\r' && c != '\n') { + c = msgString.charAt(++i); + } + } catch (ex) { + console.error("StringMsgParser:parseSIPMessagestring(): catched exception:"+ex); + break; + } + currentLine = msgString.substring(lineStart, i); + currentLine = this.trimEndOfLine(currentLine); + if (currentLine.length == 0) { + if (currentHeader != null) { + this.processHeader(currentHeader, message); + } + } + else { + if (isFirstLine) { + message = this.processFirstLine(currentLine); + } + else { + var firstChar = currentLine.charAt(0); + if (firstChar == '\t' || firstChar == ' ') { + if (currentHeader == null) { + console.error("StringMsgParser:parseSIPMessagestring(): bad header continuation."); + throw "StringMsgParser:parseSIPMessagestring(): bad header continuation."; + } + currentHeader = currentHeader+currentLine.substring(1); + } + else { + if (currentHeader != null) { + this.processHeader(currentHeader, message); + } + currentHeader = currentLine; + } + } + } + if (msgString.charAt(i) == '\r' && msgString.length > i + 1 && msgString.charAt(i + 1) == '\n') { + i++; + } + i++; + isFirstLine = false; + } while (currentLine.length > 0); + message.setSize(i); + if (this.readBody && message.getContentLength() != null) { + if (message.getContentLength().getContentLength() != 0) { + var body = msgString.substring(i); + message.setMessageContent(body, this.strict, this.computeContentLengthFromMessage, message.getContentLength().getContentLength()); + } + else if (!this.computeContentLengthFromMessage && message.getContentLength().getContentLength() == 0) { + if (this.strict) { + var last4Chars = msgString.substring(msgString.length - 4, 4); + if(!"\r\n\r\n" == last4Chars) { + console.error("StringMsgParser:parse(): extraneous characters at the end of the message",i); + throw "StringMsgParser:parse(): extraneous characters at the end of the message"; + } + } + } + } + return message; +} +StringMsgParser.prototype.parseSIPMessagebyte =function(msgBuffer){ + if (msgBuffer == null || msgBuffer.length == 0) { + return null; + } + var i = 0; + try { + while (msgBuffer[i].charCodeAt(0) < 0x20) { + i++; + } + } catch (ex) { + console.error("StringMsgParser:parseSIPMessagebyte(): catched exception:"+ex); + return null; + } + var currentLine = ""; + var currentHeader = null; + var isFirstLine = true; + var message = null; + do { + var lineStart = i; + try { + while (msgBuffer[i] != '\r' && msgBuffer[i] != '\n') { + i++; + } + } catch (ex) { + console.error("StringMsgParser:parseSIPMessagebyte(): catched exception:"+ex); + break; + } + var lineLength = i - lineStart; + try { + for(var x=0;x i + 1 && msgBuffer[i + 1] == '\n') { + i++; + } + i++; + isFirstLine = false; + } while (currentLine.length > 0); + if (message == null) { + console.error("StringMsgParser:parseSIPMessagebyte(): bad message"); + throw "StringMsgParser:parseSIPMessagebyte(): bad message"; + } + message.setSize(i); + if (this.readBody && message.getContentLength() != null + && message.getContentLength().getContentLength() != 0) { + var bodyLength = msgBuffer.length - i; + var body = new Array(); + var l=i; + for(x=0;x= 0 && line.charCodeAt(i) <= 0x20) { + i--; + } + if (i == line.length - 1) { + return line; + } + if (i == -1) { + return ""; + } + return line.substring(0, i + 1); +} + + +StringMsgParser.prototype.processFirstLine =function(firstLine){ + var message=null; + var constlength=this.SIP_VERSION_STRING.length; + var n=0; + for(var i=0;i 0) { + break; + } else { + console.error("URLParser:base_phone_number(): unexpected"+w); + throw "URLParser:base_phone_number(): unexpected"+w; + } + } + return s.toString(); +} + +URLParser.prototype.local_number =function(){ + var s = ""; + var lexer=new Lexer("",""); + var lc = 0; + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + if (la == '*'|| la == '#'|| la == '-'|| la == '.'|| la == '('| la == ')' + || lexer.isHexDigit(la)) { + this.lexer.consume(1); + s=s+la + lc++; + } else if (lc > 0) { + break; + } else { + console.error("URLParser:local_number(): unexpected"+la); + throw "URLParser:local_number(): unexpected"+la; + } + } + return s.toString(); +} + +URLParser.prototype.parseTelephoneNumber =function(inBrackets){ + var tn; + this.lexer.selectLexer("charLexer"); + var lexer=new Lexer("",""); + var c = this.lexer.lookAhead(0); + if (c == '+') { + tn = this.global_phone_number(inBrackets); + } else if (lexer.isHexDigit(c)|| c == '#'|| c == '*'|| c == '-'|| c == '.' + || c == '(' + || c == ')') { + tn = this.local_phone_number(inBrackets); + } else { + console.error("URLParser:parseTelephoneNumber(): unexpected char " + c); + throw "URLParser:parseTelephoneNumber(): unexpected char " + c; + } + return tn; +} + + +URLParser.prototype.global_phone_number =function(inBrackets){ + var tn = new TelephoneNumber(); + tn.setGlobal(true); + var nv = null; + this.lexer.match('+'); + var b = this.base_phone_number(); + tn.setPhoneNumber(b); + if (this.lexer.hasMoreChars()) { + var tok = this.lexer.lookAhead(0); + if (tok == ';' && inBrackets) { + this.lexer.consume(1); + nv = tel_parameters(); + tn.setParameters(nv); + } + } + return tn; +} + +URLParser.prototype.local_phone_number =function(inBrackets){ + var tn = new TelephoneNumber(); + tn.setGlobal(false); + var nv = null; + var b = null; + b = this.local_number(); + tn.setPhoneNumber(b); + if (this.lexer.hasMoreChars()) { + var tok = this.lexer.peekNextToken(); + switch (tok.getTokenType()) { + case this.SEMICOLON: { + if (inBrackets) { + this.lexer.consume(1); + nv = this.tel_parameters(); + tn.setParameters(nv); + } + break; + } + default: { + break; + } + } + } + return tn; +} + +URLParser.prototype.tel_parameters =function(){ + var nvList = new NameValueList(); + var nv; + while (true) { + var pname = this.paramNameOrValue(); + if (pname.toLowerCase()==("phone-context").toLowerCase()) { + nv = this.phone_context(); + } else { + if (this.lexer.lookAhead(0) == '=') { + this.lexer.consume(1); + var value = this.paramNameOrValue(); + nv = new NameValue(pname, value, false); + } else { + nv = new NameValue(pname, "", true); + } + } + nvList.set(nv); + if (this.lexer.lookAhead(0) == ';') { + this.lexer.consume(1); + } else { + return nvList; + } + } +} + +URLParser.prototype.phone_context =function(){ + this.lexer.match('='); + var la = this.lexer.lookAhead(0); + var value=null; + if (la == '+') {// global-number-digits + this.lexer.consume(1);// skip '+' + value = "+" + this.base_phone_number(); + } else if (Lexer.isAlphaDigit(la)) { + var t = this.lexer.match(Lexer.prototype.ID);// more broad than allowed + value = t.getTokenValue(); + } else { + console.error("URLParser:phone_context(): invalid phone-context:" + la); + throw "URLParser:phone_context(): invalid phone-context:" + la; + } + return new NameValue("phone-context", value, false); +} + + +URLParser.prototype.telURL =function(inBrackets){ + this.lexer.match(TokenTypes.prototype.TEL); + this.lexer.match(':'); + var tn = this.parseTelephoneNumber(inBrackets); + var telUrl = new TelURLImpl(); + telUrl.setTelephoneNumber(tn); + return telUrl; +} + +URLParser.prototype.sipURL =function(inBrackets){ + var retval = new SipUri(); + // pmusgrave - handle sips case + var nextToken = this.lexer.peekNextToken(); + var sipOrSips = TokenTypes.prototype.SIP; + var scheme = TokenNames.prototype.SIP; + if (nextToken.getTokenType() == TokenTypes.prototype.SIPS) { + sipOrSips = TokenTypes.prototype.SIPS; + scheme = TokenNames.prototype.SIPS; + } + this.lexer.match(sipOrSips); + this.lexer.match(':'); + retval.setScheme(scheme); + var startOfUser = this.lexer.markInputPosition(); + var userOrHost = this.user();// Note: user may contain ';', host may not... + var passOrPort = null; + // name:password or host:port + if (this.lexer.lookAhead() == ':') { + this.lexer.consume(1); + passOrPort = this.password(); + } + // name@hostPort + if (this.lexer.lookAhead() == '@') { + this.lexer.consume(1); + retval.setUser(userOrHost); + if (passOrPort != null) { + retval.setUserPassword(passOrPort); + } + } else { + // then userOrHost was a host, backtrack just in case a ';' was eaten... + this.lexer.rewindInputPosition(startOfUser); + } + var hnp = new HostNameParser(this.getLexer()); + var hp = hnp.hostPort(false); + retval.setHostPort(hp); + this.lexer.selectLexer("charLexer"); + while (this.lexer.hasMoreChars()) { + // If the URI is not enclosed in brackets, parameters belong to header + if (this.lexer.lookAhead(0) != ';' || !inBrackets) { + break; + } + this.lexer.consume(1); + var parms = this.uriParam(); + if (parms != null) { + retval.setUriParameter(parms); + } + } + if (this.lexer.hasMoreChars() && this.lexer.lookAhead(0) == '?') { + this.lexer.consume(1); + while (this.lexer.hasMoreChars()) { + parms = this.qheader(); + retval.setQHeader(parms); + if (this.lexer.hasMoreChars() && this.lexer.lookAhead(0) != '&') { + break; + } else { + this.lexer.consume(1); + } + } + } + + return retval; +} + +URLParser.prototype.peekScheme =function(){ + var tokens = this.lexer.peekNextToken(1); + if (tokens.length == 0) { + return null; + } + var scheme = tokens[0].getTokenValue(); + return scheme; +} + +URLParser.prototype.qheader =function(){ + var startIdx = this.lexer.ptr; + while (true) { + var la = this.lexer.lookAhead(0); + if (la == '=') { + break; + } else if (la == '') { + console.error("URLParser:qheader(): EOL reached"); + throw "URLParser:qheader(): EOL reached"; + } + this.lexer.consume(1); + } + var name=this.lexer.getBuffer().substring(startIdx, this.lexer.ptr); + this.lexer.consume(1); + var value = this.hvalue(); + return new NameValue(name, value, false); +} + +URLParser.prototype.hvalue =function(){ + var retval = ""; + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + // Look for a character that can terminate a URL. + var isValidChar = false; + switch (la) { + case '+': + case '?': + case ':': + case '[': + case ']': + case '/': + case '$': + case '_': + case '-': + case '"': + case '!': + case '~': + case '*': + case '.': + case '(': + case ')': + isValidChar = true; + } + var lexer=new Lexer("",""); + if (isValidChar || lexer.isAlphaDigit(la)) { + this.lexer.consume(1); + retval=retval+la; + } else if (la == '%') { + retval=retval+this.escaped(); + } else { + break; + } + } + return retval.toString(); +} + +URLParser.prototype.urlString =function(){ + var retval = ""; + this.lexer.selectLexer("charLexer"); + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + if (la == ' '|| la == '\t'|| la == '\n'|| la == '>'|| la == '<') { + break; + } + this.lexer.consume(0); + retval=retval+la; + } + return retval.toString(); +} + +URLParser.prototype.user =function(){ + var startIdx = this.lexer.getPtr(); + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + if (this.isUnreserved(la) || this.isUserUnreserved(la)) { + this.lexer.consume(1); + } else if (this.isEscaped()) { + this.lexer.consume(3); + } else { + break; + } + } + return this.lexer.getBuffer().substring(startIdx, this.lexer.getPtr()); +} + + +URLParser.prototype.password =function(){ + var startIdx = this.lexer.getPtr(); + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(0); + var isValidChar = false; + switch (la) { + case '&': + case '=': + case '+': + case '$': + case ',': + isValidChar = true; + } + if (isValidChar || this.isUnreserved(la)) { + this.lexer.consume(1); + } else if (this.isEscaped()) { + this.lexer.consume(3); + } else { + break; + } + } + return this.lexer.getBuffer().substring(startIdx, this.lexer.getPtr()); +} + +URLParser.prototype.parse =function(){ + return this.uriReference(true); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AddressParser . + * @see gov/nist/javax/sip/parser/AddressParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function AddressParser() { + this.classname="AddressParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var address=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", address); + } +} + +AddressParser.prototype = new Parser(); +AddressParser.prototype.constructor=AddressParser; +AddressParser.prototype.NAME_ADDR=1; +AddressParser.prototype.ADDRESS_SPEC=2; + +AddressParser.prototype.nameAddr =function(){ + if (this.lexer.lookAhead(0) == '<') { + this.lexer.consume(1); + this.lexer.selectLexer("sip_urlLexer"); + this.lexer.SPorHT(); + var uriParser = new URLParser(this.lexer); + var uri = uriParser.uriReference( true ); + var retval = new AddressImpl(); + retval.setAddressType(this.NAME_ADDR); + retval.setURI(uri); + this.lexer.SPorHT(); + this.lexer.match('>'); + return retval; + } else { + var addr = new AddressImpl(); + addr.setAddressType(this.NAME_ADDR); + var name = null; + if (this.lexer.lookAhead(0) == '\"') { + name = this.lexer.quotedString(); + this.lexer.SPorHT(); + } + else + { + name = this.lexer.getNextToken('<'); + } + addr.setDisplayName(name.trim()); + + this.lexer.match('<'); + + this.lexer.SPorHT(); + uriParser = new URLParser(this.lexer); + uri = uriParser.uriReference( true ); + retval = new AddressImpl(); + addr.setAddressType(this.NAME_ADDR); + addr.setURI(uri); + this.lexer.SPorHT(); + this.lexer.match('>'); + return addr; + } +} + +AddressParser.prototype.address =function(inclParams){ + var retval = null; + var k = 0; + while (this.lexer.hasMoreChars()) { + var la = this.lexer.lookAhead(k); + if (la == '<'|| la == '\"'|| la == ':'|| la == '/') + { + break; + } + else if (la == '') + { + console.error("AddressParser:address(): unexpected EOL"); + throw "AddressParser:parse(): unexpected EOL"; + } + else + { + k++; + } + } + la = this.lexer.lookAhead(k); + if (la == '<' || la == '\"') { + retval = this.nameAddr(); + } else if (la == ':' || la == '/') { + retval = new AddressImpl(); + var uriParser = new URLParser(this.lexer); + var uri = uriParser.uriReference( inclParams ); + retval.setAddressType(this.ADDRESS_SPEC); + retval.setURI(uri); + } else { + console.error("AddressParser:address(): bad address spec"); + throw "AddressParser:parse(): bad address spec"; + } + return retval; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ToParser . + * @see gov/nist/javax/sip/parser/ToParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ToParser() { + this.classname="ToParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var to=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", to); + } +} + +ToParser.prototype = new AddressParametersParser(); +ToParser.prototype.constructor=ToParser; + +ToParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.TO); + var to = new To(); + AddressParametersParser.prototype.parse.call(this,to); + this.lexer.match('\n'); + return to; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP FromParser . + * @see gov/nist/javax/sip/parser/FromParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function FromParser() { + this.classname="FromParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var from=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", from); + } +} + +FromParser.prototype = new AddressParametersParser(); +FromParser.prototype.constructor=FromParser; + +FromParser.prototype.parse =function(){ + var from = new From(); + this.lexer.match(TokenTypes.prototype.FROM); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); + AddressParametersParser.prototype.parse.call(this,from); + this.lexer.match('\n'); + return from; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP CSeqParser . + * @see gov/nist/javax/sip/parser/CSeqParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ + +function CSeqParser() { + this.classname="CSeqParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var buffer=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", buffer); + + } +} + +CSeqParser.prototype = new HeaderParser(); +CSeqParser.prototype.constructor=CSeqParser; + +CSeqParser.prototype.parse =function(){ + var c = new CSeq(); + this.lexer.match(TokenTypes.prototype.CSEQ); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); + var number = this.lexer.number(); + c.setSeqNumber(number); + this.lexer.SPorHT(); + var siprequest=new SIPRequest(); + var m = siprequest.getCannonicalName(this.method()); + c.setMethod(m); + this.lexer.SPorHT(); + this.lexer.match('\n'); + return c; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ViaParser . + * @see gov/nist/javax/sip/parser/ViaParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ViaParser() { + this.classname="ViaParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var via=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", via); + } +} + +ViaParser.prototype = new HeaderParser(); +ViaParser.prototype.constructor=ViaParser; +ViaParser.prototype.RECEIVED="received"; +ViaParser.prototype.BRANCH="branch"; + +ViaParser.prototype.parseVia =function(v){ + this.lexer.match(TokenTypes.prototype.ID); + var protocolName = this.lexer.getNextToken(); + this.lexer.SPorHT(); + this.lexer.match('/'); + this.lexer.SPorHT(); + this.lexer.match(TokenTypes.prototype.ID); + this.lexer.SPorHT(); + var protocolVersion = this.lexer.getNextToken(); + this.lexer.SPorHT(); + this.lexer.match('/'); + this.lexer.SPorHT(); + this.lexer.match(TokenTypes.prototype.ID); + this.lexer.SPorHT(); + var transport = this.lexer.getNextToken(); + this.lexer.SPorHT(); + var protocol = new Protocol(); + protocol.setProtocolName(protocolName.getTokenValue()); + protocol.setProtocolVersion(protocolVersion.getTokenValue()); + protocol.setTransport(transport.getTokenValue()); + v.setSentProtocol(protocol); + var hnp = new HostNameParser(this.getLexer()); + var hostPort = hnp.hostPort( true ); + v.setSentBy(hostPort); + this.lexer.SPorHT(); + while (this.lexer.lookAhead(0) == ';') { + this.lexer.consume(1); + this.lexer.SPorHT(); + var nameValue = this.nameValue(); + var name = nameValue.getName(); + if (name==this.BRANCH) { + var branchId = nameValue.getValueAsObject(); + if (branchId == null) + { + console.error("ViaParser:parseVia(): null branch Id", this.lexer.getPtr()); + throw "ViaParser:parseVia(): null branch Id"+ this.lexer.getPtr(); + } + } + v.setParameter_nv(nameValue); + this.lexer.SPorHT(); + } + if (this.lexer.lookAhead(0) == '(') { + this.lexer.selectLexer("charLexer"); + this.lexer.consume(1); + var comment = ""; + while (true) { + var ch = this.lexer.lookAhead(0); + if (ch == ')') { + this.lexer.consume(1); + break; + } else if (ch == '\\') { + // Escaped character + var tok = this.lexer.getNextToken(); + comment=comment+tok.getTokenValue(); + this.lexer.consume(1); + tok = this.lexer.getNextToken(); + comment=comment+tok.getTokenValue(); + this.lexer.consume(1); + } else if (ch == '\n') { + break; + } else { + comment=comment+ch; + this.lexer.consume(1); + } + } + v.setComment(comment.toString()); + } +} + +ViaParser.prototype.nameValue =function(){ + this.lexer.match(LexerCore.prototype.ID); + var name = this.lexer.getNextToken(); + this.lexer.SPorHT(); + try { + var quoted = false; + var la = this.lexer.lookAhead(0); + if (la == '=') { + this.lexer.consume(1); + this.lexer.SPorHT(); + var str = null; + if (name.getTokenValue().toLowerCase()==this.RECEIVED.toLowerCase()) { + str = this.lexer.byteStringNoSemicolon(); + } else { + if (this.lexer.lookAhead(0) == '\"') { + str = this.lexer.quotedString(); + quoted = true; + } else { + this.lexer.match(LexerCore.prototype.ID); + var value = this.lexer.getNextToken(); + str = value.getTokenValue(); + } + } + var nv = new NameValue(name.getTokenValue().toLowerCase(), str); + if (quoted) + { + nv.setQuotedValue(); + } + return nv; + } else { + return new NameValue(name.getTokenValue().toLowerCase(), null); + } + } catch (ex) { + console.error("ViaParser:nameValue(): catched exception:"+ex); + return new NameValue(name.getTokenValue(), null); + } +} + +ViaParser.prototype.parse =function(){ + var viaList = new ViaList(); + this.lexer.match(TokenTypes.prototype.VIA); + this.lexer.SPorHT(); // ignore blanks + this.lexer.match(':'); // expect a colon. + this.lexer.SPorHT(); // ingore blanks. + while (true) { + var v = new Via(); + this.parseVia(v); + viaList.add(v); + this.lexer.SPorHT(); // eat whitespace. + if (this.lexer.lookAhead(0) == ',') { + this.lexer.consume(1); // Consume the comma + this.lexer.SPorHT(); // Ignore space after. + } + if (this.lexer.lookAhead(0) == '\n') + { + break; + } + } + this.lexer.match('\n'); + return viaList; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ContactParser . + * @see gov/nist/javax/sip/parser/ContactParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ContactParser() { + this.classname="ContactParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var contact=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", contact); + } +} + +ContactParser.prototype = new AddressParametersParser(); +ContactParser.prototype.constructor=ContactParser; + +ContactParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.CONTACT); + var retval = new ContactList(); + while (true) { + var contact = new Contact(); + if (this.lexer.lookAhead(0) == '*') { + var next = this.lexer.lookAhead(1); + if (next == ' ' || next == '\t' || next == '\r' || next == '\n') { + this.lexer.match('*'); + contact.setWildCardFlag(true); + } else { + AddressParametersParser.prototype.parse.call(this,contact); + } + } else { + AddressParametersParser.prototype.parse.call(this,contact); + } + retval.add(contact); + this.lexer.SPorHT(); + var la = this.lexer.lookAhead(0); + if (la == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + } + else if (la == '\n' || la == '') + { + break; + } + else + { + console.error("ContactParser:address(): unexpected char"); + throw "ContactParser:parse(): unexpected char"; + } + } + return retval; +} + +ContactParser.prototype.nameValue =function(){ + this.lexer.match(LexerCore.prototype.ID); + var name = this.lexer.getNextToken(); + this.lexer.SPorHT(); + try { + var quoted = false; + var la = this.lexer.lookAhead(0); + if (la == '=') { + this.lexer.consume(1); + this.lexer.SPorHT(); + var str = null; + if (this.lexer.lookAhead(0) == '\"') { + str = this.lexer.quotedString(); + quoted = true; + } else { + str = this.lexer.byteStringNoSemicolon(); + } + var nv = new NameValue(name.getTokenValue().toLowerCase(), str); + if (quoted) + { + nv.setQuotedValue(); + } + return nv; + } else { + return new NameValue(name.getTokenValue().toLowerCase(), null); + } + } catch (ex) { + console.error("ContactParser:nameValue(): catched exception:"+ex); + return new NameValue(name.getTokenValue(), null); + } +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ContentTypeParser . + * @see gov/nist/javax/sip/parser/ContentTypeParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ContentTypeParser() { + this.classname="ContentTypeParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var contentType=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", contentType); + } +} + +ContentTypeParser.prototype = new ParametersParser(); +ContentTypeParser.prototype.constructor=ContentTypeParser; + +ContentTypeParser.prototype.parse =function(){ + var contentType = new ContentType(); + this.headerName(TokenTypes.prototype.CONTENT_TYPE); + this.lexer.match(TokenTypes.prototype.ID); + var type = this.lexer.getNextToken(); + this.lexer.SPorHT(); + contentType.setContentType(type.getTokenValue()); + this.lexer.match('/'); + this.lexer.match(TokenTypes.prototype.ID); + var subType = this.lexer.getNextToken(); + this.lexer.SPorHT(); + contentType.setContentSubType(subType.getTokenValue()); + ParametersParser.prototype.parse.call(this,contentType); + this.lexer.match('\n'); + return contentType; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ContentLengthParser . + * @see gov/nist/javax/sip/parser/ContentLengthParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ContentLengthParser() { + this.classname="ContentLengthParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var contentLength=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", contentLength); + } +} + +ContentLengthParser.prototype = new HeaderParser(); +ContentLengthParser.prototype.constructor=ContentLengthParser; + +ContentLengthParser.prototype.parse =function(){ + var contentLength = new ContentLength(); + this.headerName(TokenTypes.prototype.CONTENT_LENGTH); + var number = this.lexer.number(); + contentLength.setContentLength(number); + this.lexer.SPorHT(); + this.lexer.match('\n'); + return contentLength; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AuthorizationParser . + * @see gov/nist/javax/sip/parser/AuthorizationParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function AuthorizationParser() { + this.classname="AuthorizationParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var authorization=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", authorization); + } +} + +AuthorizationParser.prototype = new ChallengeParser(); +AuthorizationParser.prototype.constructor=AuthorizationParser; + +AuthorizationParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.AUTHORIZATION); + var auth = new Authorization(); + ChallengeParser.prototype.parse.call(this,auth);//used to call the method of challengeparser + return auth; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP WWWAuthenticateParser . + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function WWWAuthenticateParser() { + this.classname="WWWAuthenticateParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var wwwAuthenticate=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", wwwAuthenticate); + } +} + +WWWAuthenticateParser.prototype = new ChallengeParser(); +WWWAuthenticateParser.prototype.constructor=WWWAuthenticateParser; + +WWWAuthenticateParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.WWW_AUTHENTICATE); + var wwwAuthenticate = new WWWAuthenticate(); + ChallengeParser.prototype.parse.call(this,wwwAuthenticate); + return wwwAuthenticate; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP CallIDParser . + * @see gov/nist/javax/sip/parser/CallIDParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function CallIDParser() { + this.classname="CallIDParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var buffer=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", buffer); + } +} + +CallIDParser.prototype = new HeaderParser(); +CallIDParser.prototype.constructor=CallIDParser; + +CallIDParser.prototype.parse =function(){ + this.lexer.match(TokenTypes.prototype.CALL_ID); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); + var callID = new CallID(); + this.lexer.SPorHT(); + var rest = this.lexer.getRest(); + callID.setCallId(rest.trim()); + return callID; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP RouteParser . + * @see gov/nist/javax/sip/parser/RouteParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function RouteParser() { + this.classname="RouteParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var route=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", route); + } +} + +RouteParser.prototype = new AddressParametersParser(); +RouteParser.prototype.constructor=RouteParser; + +RouteParser.prototype.parse =function(){ + var routeList = new RouteList(); + this.lexer.match(TokenTypes.prototype.ROUTE); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); + while (true) { + var route = new Route(); + AddressParametersParser.prototype.parse.call(this,route); + routeList.add(route); + this.lexer.SPorHT(); + var la = this.lexer.lookAhead(0); + if (la == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + } + else if (la == '\n') + { + break; + } + else + { + console.error("RouteParser:parse(): unexpected char"); + throw "RouteParser:parse(): unexpected char"; + } + } + return routeList; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP RecordRouteParser . + * @see gov/nist/javax/sip/parser/RecordRouteParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function RecordRouteParser() { + this.classname="RecordRouteParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var recordRoute=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", recordRoute); + } +} + +RecordRouteParser.prototype = new AddressParametersParser(); +RecordRouteParser.prototype.constructor=RecordRouteParser; + +RecordRouteParser.prototype.parse =function(){ + var recordRouteList = new RecordRouteList(); + this.lexer.match(TokenTypes.prototype.RECORD_ROUTE); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); + + while (true) { + var recordRoute = new RecordRoute(); + AddressParametersParser.prototype.parse.call(this,recordRoute); + recordRouteList.add(recordRoute); + this.lexer.SPorHT(); + var la = this.lexer.lookAhead(0); + if (la == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + } + else if (la == '\n') + { + break; + } + else + { + console.error("RecordRouteParser:parse(): unexpected char"); + throw "RecordRouteParser:parse(): unexpected char"; + } + } + return recordRouteList; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ProxyAuthenticateParser . + * @see gov/nist/javax/sip/parser/ProxyAuthenticateParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ProxyAuthenticateParser() { + this.classname="ProxyAuthenticateParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var proxyAuthenticate=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", proxyAuthenticate); + } +} + +ProxyAuthenticateParser.prototype = new ChallengeParser(); +ProxyAuthenticateParser.prototype.constructor=ProxyAuthenticateParser; + +ProxyAuthenticateParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.PROXY_AUTHENTICATE); + var proxyAuth = new ProxyAuthenticate(); + ChallengeParser.prototype.parse.call(this,proxyAuth); + return proxyAuth; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ProxyAuthorizationParser . + * @see gov/nist/javax/sip/parser/ProxyAuthorizationParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ProxyAuthorizationParser() { + this.classname="ProxyAuthorizationParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var proxyAuthorization=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", proxyAuthorization); + } +} + +ProxyAuthorizationParser.prototype = new ChallengeParser(); +ProxyAuthorizationParser.prototype.constructor=ProxyAuthorizationParser; + +ProxyAuthorizationParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.PROXY_AUTHORIZATION); + var proxyAuth = new ProxyAuthorization(); + ChallengeParser.prototype.parse.call(this,proxyAuth); + return proxyAuth; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP TimeStampParser . + * @see gov/nist/javax/sip/parser/TimeStampParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function TimeStampParser() { + this.classname="TimeStampParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var timeStamp=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", timeStamp); + } +} + +TimeStampParser.prototype = new HeaderParser(); +TimeStampParser.prototype.constructor=TimeStampParser; +TimeStampParser.prototype.TIMESTAMP="Timestamp"; + +TimeStampParser.prototype.parse =function(){ + var timeStamp = new TimeStamp(); + this.headerName(TokenTypes.prototype.TIMESTAMP); + timeStamp.setHeaderName(this.TIMESTAMP); + this.lexer.SPorHT(); + var firstNumber = this.lexer.number(); + if (this.lexer.lookAhead(0) == '.') { + this.lexer.match('.'); + var secondNumber = this.lexer.number(); + + var s = firstNumber + "." + secondNumber; + var ts = s; + timeStamp.setTimeStamp(ts); + } else { + ts = firstNumber; + timeStamp.setTime(ts); + } + + this.lexer.SPorHT(); + if (this.lexer.lookAhead(0) != '\n') + { + firstNumber = this.lexer.number(); + } + if (this.lexer.lookAhead(0) == '.') { + this.lexer.match('.'); + secondNumber = this.lexer.number(); + s = firstNumber + "." + secondNumber; + ts = s; + timeStamp.setDelay(ts); + } else { + ts = firstNumber; + timeStamp.setDelay(ts); + } + return timeStamp; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP UserAgentParser . + * @see gov/nist/javax/sip/parser/UserAgentParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function UserAgentParser() { + this.classname="UserAgentParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var userAgent=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", userAgent); + } +} + +UserAgentParser.prototype = new HeaderParser(); +UserAgentParser.prototype.constructor=UserAgentParser; + +UserAgentParser.prototype.parse =function(){ + var userAgent = new UserAgent(); + this.headerName(TokenTypes.prototype.USER_AGENT); + if (this.lexer.lookAhead(0) == '\n') + { + console.error("UserAgentParser:parse(): empty header"); + throw "UserAgentParser:parse(): empty header"; + } + + while (this.lexer.lookAhead(0) != '\n' + && this.lexer.lookAhead(0) != '') { + if (this.lexer.lookAhead(0) == '(') { + var comment = this.lexer.comment(); + userAgent.addProductToken('(' + comment + ')'); + } else { + this.getLexer().SPorHT(); + var product = this.lexer.byteStringNoSlash(); + if ( product == null ) { + console.error("UserAgentParser:parse(): expected product string"); + throw "UserAgentParser:parse():expected product string"; + } + var productSb = product; + if (this.lexer.peekNextToken().getTokenValue() == '/') { + this.lexer.match('/'); + this.getLexer().SPorHT(); + var productVersion = this.lexer.byteStringNoWhiteSpace(); + if ( productVersion == null ) { + console.error("UserAgentParser:parse(): expected product version"); + throw "UserAgentParser:parse(): expected product version"; + } + productSb=productSb+"/"+productVersion; + } + userAgent.addProductToken(productSb.toString()); + } + this.lexer.SPorHT(); + } + return userAgent; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SupportedParser . + * @see gov/nist/javax/sip/parser/SupportedParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function SupportedParser() { + this.classname="SupportedParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var supported=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", supported); + } +} + +SupportedParser.prototype = new HeaderParser(); +SupportedParser.prototype.constructor=SupportedParser; +SupportedParser.prototype.SUPPORTED="Supported"; + +SupportedParser.prototype.parse =function(){ + var supportedList = new SupportedList(); + this.headerName(TokenTypes.prototype.SUPPORTED); + while (this.lexer.lookAhead(0) != '\n') { + this.lexer.SPorHT(); + var supported = new Supported(); + supported.setHeaderName(this.SUPPORTED); + this.lexer.match(TokenTypes.prototype.ID); + var token = this.lexer.getNextToken(); + supported.setOptionTag(token.getTokenValue()); + this.lexer.SPorHT(); + supportedList.add(supported); + while (this.lexer.lookAhead(0) == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + supported = new Supported(); + this.lexer.match(TokenTypes.prototype.ID); + token = this.lexer.getNextToken(); + supported.setOptionTag(token.getTokenValue()); + this.lexer.SPorHT(); + supportedList.add(supported); + } + + } + return supportedList; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ServerParser . + * @see gov/nist/javax/sip/parser/ServerParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ServerParser() { + this.classname="ServerParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var server=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", server); + } +} + +ServerParser.prototype = new HeaderParser(); +ServerParser.prototype.constructor=ServerParser; + +ServerParser.prototype.parse =function(){ + var server = new Server(); + this.headerName(TokenTypes.prototype.SERVER); + if (this.lexer.lookAhead(0) == '\n') + { + console.error("ServerParser:parse(): empty header"); + throw "ServerParser:parse(): empty header"; + } + server.addProductToken(this.lexer.getRest().trim()); + return server; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SubjectParser . + * @see gov/nist/javax/sip/parser/SubjectParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function SubjectParser() { + this.classname="SubjectParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var subject=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", subject); + } +} + +SubjectParser.prototype = new HeaderParser(); +SubjectParser.prototype.constructor=SubjectParser; + +SubjectParser.prototype.parse =function(){ + var subject = new Subject(); + this.headerName(TokenTypes.prototype.SUBJECT); + this.lexer.SPorHT(); + var s = this.lexer.getRest(); + subject.setSubject(s.trim()); + return subject; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP MaxForwardsParser . + * @see gov/nist/javax/sip/parser/MaxForwardsParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function MaxForwardsParser() { + this.classname="MaxForwardsParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var contentLength=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", contentLength); + } +} + +MaxForwardsParser.prototype = new HeaderParser(); +MaxForwardsParser.prototype.constructor=MaxForwardsParser; + +MaxForwardsParser.prototype.parse =function(){ + var contentLength = new MaxForwards(); + this.headerName(TokenTypes.prototype.MAX_FORWARDS); + var number = this.lexer.number(); + contentLength.setMaxForwards(number); + this.lexer.SPorHT(); + this.lexer.match("\n"); + return contentLength; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ReasonParser . + * @see gov/nist/javax/sip/parser/ReasonParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ReasonParser() { + this.classname="ReasonParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var reason=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", reason); + } +} + +ReasonParser.prototype = new ParametersParser(); +ReasonParser.prototype.constructor=ReasonParser; + +ReasonParser.prototype.parse =function(){ + var reasonList = new ReasonList(); + this.headerName(TokenTypes.prototype.REASON); + this.lexer.SPorHT(); + while (this.lexer.lookAhead(0) != '\n') { + var reason = new Reason(); + this.lexer.match(TokenTypes.prototype.ID); + var token = this.lexer.getNextToken(); + var value = token.getTokenValue(); + reason.setProtocol(value); + ParametersParser.prototype.parse.call(this,reason); + reasonList.add(reason); + if (this.lexer.lookAhead(0) == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + } + else + { + this.lexer.SPorHT(); + } + return reasonList; + } +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP RequestLineParser . + * @see gov/nist/javax/sip/parser/RequestLineParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function RequestLineParser() { + this.classname="RequestLineParser"; + if(typeof arguments[0]=="string") + { + var requestLine=arguments[0]; + this.lexer = new Lexer("method_keywordLexer", requestLine); + } + else if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("method_keywordLexer"); + } +} + +RequestLineParser.prototype = new Parser(); +RequestLineParser.prototype.constructor=RequestLineParser; + +RequestLineParser.prototype.parse =function(){ + var retval = new RequestLine(); + var m = this.method(); + this.lexer.SPorHT(); + retval.setMethod(m); + this.lexer.selectLexer("sip_urlLexer"); + var urlParser = new URLParser(this.getLexer()); + var url = urlParser.uriReference(true); + this.lexer.SPorHT(); + retval.setUri(url); + this.lexer.selectLexer("request_lineLexer"); + var v = this.sipVersion(); + retval.setSipVersion(v); + this.lexer.SPorHT(); + this.lexer.match('\n'); + return retval; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ExpiresParser . + * @see gov/nist/javax/sip/parser/ExpiresParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ + +function ExpiresParser() { + this.classname="ExpiresParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var text=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", text); + } +} + +ExpiresParser.prototype = new HeaderParser(); +ExpiresParser.prototype.constructor=ExpiresParser; + +ExpiresParser.prototype.parse =function(){ + var expires = new Expires(); + this.lexer.match(TokenTypes.prototype.EXPIRES); + this.lexer.SPorHT(); + this.lexer.match(':'); + this.lexer.SPorHT(); + var nextId = this.lexer.getNextId(); + this.lexer.match('\n'); + var delta = nextId+0; + expires.setExpires(delta); + return expires; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP EventParser . + * @see gov/nist/javax/sip/parser/EventParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ + +function EventParser() { + this.classname="EventParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var event=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", event); + } +} + +EventParser.prototype = new ParametersParser(); +EventParser.prototype.constructor=EventParser; + +EventParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.EVENT); + this.lexer.SPorHT(); + var event = new Event(); + this.lexer.match(TokenTypes.prototype.ID); + var token = this.lexer.getNextToken(); + var value = token.getTokenValue(); + event.setEventType(value); + ParametersParser.prototype.parse.call(this,event); + this.lexer.SPorHT(); + this.lexer.match('\n'); + return event; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP StatusLineParser . + * @see gov/nist/javax/sip/parser/StatusLineParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function StatusLineParser() { + this.classname="StatusLineParser"; + if(typeof arguments[0]=="string") + { + var statusLine=arguments[0]; + this.lexer = new Lexer("status_lineLexer", statusLine); + } + else if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("status_lineLexer"); + } +} + +StatusLineParser.prototype = new Parser(); +StatusLineParser.prototype.constructor=StatusLineParser; + +StatusLineParser.prototype.statusCode =function(){ + var scode = this.lexer.number(); + var retval = scode; + return retval; +} +StatusLineParser.prototype.reasonPhrase =function(){ + return this.lexer.getRest().trim(); +} +StatusLineParser.prototype.parse =function(){ + var retval = new StatusLine(); + var version = this.sipVersion(); + retval.setSipVersion(version); + this.lexer.SPorHT(); + var scode = this.statusCode(); + retval.setStatusCode(scode); + this.lexer.SPorHT(); + var rp = this.reasonPhrase(); + retval.setReasonPhrase(rp); + this.lexer.SPorHT(); + return retval; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ContentDispositionParser . + * @see gov/nist/javax/sip/parser/ContentDispositionParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ContentDispositionParser() { + this.classname="ContentDispositionParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var contentDisposition=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", contentDisposition); + } +} + +ContentDispositionParser.prototype = new ParametersParser(); +ContentDispositionParser.prototype.constructor=ContentDispositionParser; +ContentDispositionParser.prototype.CONTENT_DISPOSITION="Content-Disposition"; + +ContentDispositionParser.prototype.parse =function(){ + this.headerName(TokenTypes.prototype.CONTENT_DISPOSITION); + + var cd = new ContentDisposition(); + cd.setHeaderName(this.CONTENT_DISPOSITION); + + this.lexer.SPorHT(); + this.lexer.match(TokenTypes.prototype.ID); + + var token = this.lexer.getNextToken(); + cd.setDispositionType(token.getTokenValue()); + this.lexer.SPorHT(); + ParametersParser.prototype.parse.call(this,cd); + + this.lexer.SPorHT(); + this.lexer.match('\n'); + + return cd; +} + + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AllowParser . + * @see gov/nist/javax/sip/parser/AllowParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ + +function AllowParser() { + this.classname="AllowParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var buffer=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", buffer); + } +} + +AllowParser.prototype = new HeaderParser(); +AllowParser.prototype.constructor=AllowParser; +AllowParser.prototype.ALLOW="Allow"; + +AllowParser.prototype.parse =function(){ + var list = new AllowList(); + this.headerName(TokenTypes.prototype.ALLOW); + var allow = new Allow(); + allow.setHeaderName(this.ALLOW); + this.lexer.SPorHT(); + this.lexer.match(TokenTypes.prototype.ID); + var token = this.lexer.getNextToken(); + allow.setMethod(token.getTokenValue()); + list.add(allow); + this.lexer.SPorHT(); + while (this.lexer.lookAhead(0) == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + allow = new Allow(); + this.lexer.match(TokenTypes.prototype.ID); + token = this.lexer.getNextToken(); + allow.setMethod(token.getTokenValue()); + list.add(allow); + this.lexer.SPorHT(); + } + this.lexer.SPorHT(); + this.lexer.match('\n'); + return list; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP AllowEventsParser . + * @see gov/nist/javax/sip/parser/AllowEventsParser.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function AllowEventsParser() { + this.classname="AllowEventsParser"; + if(typeof arguments[0]=="object") + { + var lexer=arguments[0]; + this.lexer = lexer; + this.lexer.selectLexer("command_keywordLexer"); + } + else if(typeof arguments[0]=="string") + { + var allowEvents=arguments[0]; + this.lexer = new Lexer("command_keywordLexer", allowEvents); + } +} + +AllowEventsParser.prototype = new HeaderParser(); +AllowEventsParser.prototype.constructor=AllowEventsParser; +AllowEventsParser.prototype.ALLOW_EVENTS="Allow-Events"; + +AllowEventsParser.prototype.parse =function(){ + var list = new AllowEventsList(); + this.headerName(TokenTypes.prototype.ALLOW_EVENTS); + var allowEvents = new AllowEvents(); + allowEvents.setHeaderName(this.ALLOW_EVENTS); + this.lexer.SPorHT(); + this.lexer.match(TokenTypes.prototype.ID); + var token = this.lexer.getNextToken(); + allowEvents.setEventType(token.getTokenValue()); + list.add(allowEvents); + this.lexer.SPorHT(); + while (this.lexer.lookAhead(0) == ',') { + this.lexer.match(','); + this.lexer.SPorHT(); + allowEvents = new AllowEvents(); + this.lexer.match(TokenTypes.prototype.ID); + token = this.lexer.getNextToken(); + allowEvents.setEventType(token.getTokenValue()); + list.add(allowEvents); + this.lexer.SPorHT(); + } + this.lexer.SPorHT(); + this.lexer.match('\n'); + return list; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ParserFactory . + * @see gov/nist/javax/sip/parser/ParserFactory.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function ParserFactory() { + this.classname="ParserFactory"; + this.parserTable=new Array(); + this.constructorArgs=null; + //i use type String to replace the type of class in Java and creat the object when it is in need. + this.parserConstructorCache=new Array(); + this.put(this.parserTable,"t",new ToParser().classname); + this.put(this.parserTable,"To".toLowerCase(), new ToParser().classname); + this.put(this.parserTable,"From".toLowerCase(),new FromParser().classname); + this.put(this.parserTable,"f",new FromParser().classname); + this.put(this.parserTable,"CSeq".toLowerCase(),new CSeqParser().classname); + this.put(this.parserTable,"Via".toLowerCase(),new ViaParser().classname); + this.put(this.parserTable,"v",new ViaParser().classname); + this.put(this.parserTable,"Contact".toLowerCase(),new ContactParser().classname); + this.put(this.parserTable,"m",new ContactParser().classname); + this.put(this.parserTable,"Content-Type".toLowerCase(),new ContentTypeParser().classname); + this.put(this.parserTable,"c",new ContentTypeParser().classname); + this.put(this.parserTable,"Content-Length".toLowerCase(),new ContentLengthParser().classname); + this.put(this.parserTable,"l",new ContentLengthParser().classname); + this.put(this.parserTable,"Authorization".toLowerCase(),new AuthorizationParser().classname); + this.put(this.parserTable,"WWW-Authenticate".toLowerCase(),new WWWAuthenticateParser().classname); + this.put(this.parserTable,"Call-ID".toLowerCase(),new CallIDParser().classname); + this.put(this.parserTable,"i",new CallIDParser().classname); + this.put(this.parserTable,"Route".toLowerCase(),new RouteParser().classname); + this.put(this.parserTable,"Record-Route".toLowerCase(),new RecordRouteParser().classname); + this.put(this.parserTable,"Proxy-Authorization".toLowerCase(),new ProxyAuthorizationParser().classname); + this.put(this.parserTable,"Proxy-Authenticate".toLowerCase(),new ProxyAuthenticateParser().classname); + this.put(this.parserTable,"Timestamp".toLowerCase(),new TimeStampParser().classname); + this.put(this.parserTable,"User-Agent".toLowerCase(),new UserAgentParser().classname); + this.put(this.parserTable,"Supported".toLowerCase(),new SupportedParser().classname); + this.put(this.parserTable,"k",new SupportedParser().classname); + this.put(this.parserTable,"Server".toLowerCase(),new ServerParser().classname); + this.put(this.parserTable,"Subject".toLowerCase(),new SubjectParser().classname); + this.put(this.parserTable,"s",new SubjectParser().classname); + this.put(this.parserTable,"Max-Forwards".toLowerCase(),new MaxForwardsParser().classname); + this.put(this.parserTable,"Reason".toLowerCase(),new ReasonParser().classname); + this.put(this.parserTable,"Expires".toLowerCase(),new ExpiresParser().classname); + this.put(this.parserTable,"Event".toLowerCase(),new EventParser().classname); + this.put(this.parserTable,"o",new EventParser().classname); + this.put(this.parserTable,"Content-Disposition".toLowerCase(),new ContentDispositionParser().classname); + this.put(this.parserTable,"Allow".toLowerCase(),new AllowParser().classname); + this.put(this.parserTable,"Allow-Events".toLowerCase(),new AllowEventsParser().classname); + this.put(this.parserTable,"u",new AllowEventsParser().classname); +} + +ParserFactory.prototype.createParser =function(line){ + var lexer=new Lexer("",""); + var headerName = lexer.getHeaderName(line); + var headerValue = lexer.getHeaderValue(line); + if (headerName == null || headerValue == null) { + console.error("ParserFactory:createParser(): the header name or value is null"); + throw "ParserFactory:createParser(): the header name or value is null"; + } + var parserClass = null; + var lowercaseHeadervalue=headerName.toLowerCase(); + for(var i=0;i MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; + + } +} +SIPMessage.prototype.hasContent =function(){ + return this.messageContent != null; +} + +SIPMessage.prototype.getHeaders =function(){ + if(arguments.length!=0) + { + var headerName=arguments[0]; + if (headerName == null) + { + console.error("MessageFactoryImpl:getHeaders(): headerName header!"); + throw "MessageFactoryImpl:getHeaders(): null headerName!"; + } + var sipHeader = null; + for(var i=0;i) (((SIPHeaderList< ? >) sipHeader).getHeaderList()); + } + else { + var ll = new Array(); + ll.push(sipHeader); + return ll; + } +} + +SIPMessage.prototype.hasHeader =function(headerName){ + var l=null; + for(var i=0;i100 && code<300 ) { + return this.isDialogCreating( this.getMethod() ) && this.getToTag() == null; + } + else { + return false; + } +} + +SIPRequest.prototype.createCancelRequest =function(){ + if (this.getMethod()!=this.INVITE) + { + console.error("SIPRequest:createCancelRequest(): Attempt to create CANCEL for " + this.getMethod()); + throw "SIPRequest:createCancelRequest(): Attempt to create CANCEL for " + this.getMethod(); + } + var cancel = new SIPRequest(); + cancel.setRequestLine( this.requestLine); + cancel.setMethod(this.CANCEL); + cancel.setHeader(this.callIdHeader); + cancel.setHeader(this.toHeader); + cancel.setHeader(this.cSeqHeader); + cancel.getCSeq().setMethod(this.CANCEL); + cancel.setHeader( this.fromHeader); + cancel.addFirst( this.getTopmostVia()); + cancel.setHeader( this.maxForwardsHeader); + if (this.getRouteHeaders() != null) { + cancel.setHeader(this.getRouteHeaders()); + } + if (MessageFactoryImpl.prototype.getDefaultUserAgentHeader() != null) { + cancel.setHeader(MessageFactoryImpl.prototype.getDefaultUserAgentHeader()); + } + return cancel; +} + +SIPRequest.prototype.createAckRequest =function() +{ + if(arguments.length==1) + { + var responseToHeader=arguments[0]; + return this.createAckRequest_argu1(responseToHeader); + } + else + { + return this.createACKRequest_argu0(); + } +} +SIPRequest.prototype.createAckRequest_argu1 =function(responseToHeader){ + var newRequest = new SIPRequest(); + var nextHeader =null; + newRequest.setRequestLine(this.requestLine); + newRequest.setMethod(this.ACK); + var headerIterator = this.getHeaders(); + for(var i=0;i 699) + { + console.error("SIPResponse:setStatusCode(): bad status code"); + throw "SIPResponse:setStatusCode(): bad status code"; + } + if (this.statusLine == null) + { + this.statusLine = new StatusLine(); + } + this.statusLine.setStatusCode(statusCode); +} + +SIPResponse.prototype.getStatusLine =function(){ + return this.statusLine; +} + +SIPResponse.prototype.getStatusCode =function(){ + return this.statusLine.getStatusCode(); +} + +SIPResponse.prototype.setReasonPhrase =function(reasonPhrase){ + if (reasonPhrase == null) + { + console.error("SIPResponse:setReasonPhrase(): bad reason phrase"); + throw "SIPResponse:setReasonPhrase(): bad reason phrase"; + } + if (this.statusLine == null) + { + this.statusLine = new StatusLine(); + } + this.statusLine.setReasonPhrase(reasonPhrase); +} + +SIPResponse.prototype.getReasonPhrase_argu0 =function(){ + if (this.statusLine == null || this.statusLine.getReasonPhrase() == null) + { + return ""; + } + else + { + return this.statusLine.getReasonPhrase(); + } +} + +SIPResponse.prototype.isFinalResponse =function(rc){ + if(rc==null) + { + rc=this.statusLine.getStatusCode(); + } + if(rc >= 200 && rc < 700) + { + return true; + } + else + { + return false; + } +} + +SIPResponse.prototype.setStatusLine =function(sl){ + this.statusLine = sl; +} + +SIPResponse.prototype.checkHeaders =function(){ + + if (this.getCSeq() == null) { + console.error("SIPResponse:checkHeaders(): "+ this.CSeq+ " is missing ", 0); + throw "SIPResponse:checkHeaders(): "+ this.CSeq+ " is missing "; + } + if (this.getTo() == null) { + console.error("SIPResponse:checkHeaders(): "+ this.To+ " is missing ", 0); + throw "SIPResponse:checkHeaders(): "+ this.To+ " is missing "; + } + if (this.getFrom() == null) { + console.error("SIPResponse:checkHeaders(): "+ this.From+ " is missing ", 0); + throw "SIPResponse:checkHeaders(): "+ this.From+ " is missing "; + } + if (this.getViaHeaders() == null) { + console.error("SIPResponse:checkHeaders(): "+ this.Via+ " is missing ", 0); + throw "SIPResponse:checkHeaders(): "+ this.Via+ " is missing "; + } + if (this.getCallId() == null) { + console.error("SIPResponse:checkHeaders(): "+ this.CallID+ " is missing ", 0); + throw "SIPResponse:checkHeaders(): "+ this.CallID+ " is missing "; + } + if (this.getStatusCode() > 699) { + console.error("SIPResponse:checkHeaders(): unknown error code!" + this.getStatusCode(), 0); + throw "SIPResponse:checkHeaders(): unknown error code!" + this.getStatusCode(); + } +} + +SIPResponse.prototype.encode =function(){ + var retval; + if (this.statusLine != null) + { + retval = this.statusLine.encode() + this.superencode(); + } + else + { + retval = this.superencode(); + } + return retval ; +} + +SIPResponse.prototype.encodeMessage =function(){ + var retval; + if (this.statusLine != null) + { + retval = this.statusLine.encode() + this.encodeSIPHeaders(); + } + else + { + retval = this.encodeSIPHeaders(); + } + return retval ; +} + +SIPResponse.prototype.superencode =function(){ + var encoding = ""; + for(var i=0;i= 300 ) { + branch = this.getTopmostVia().getBranch(); // non-2xx ACK uses same branch + } + else { + branch = Utils.prototype.generateBranchId(); // 2xx ACK gets new branch + } + } + else if (method==this.CANCEL) { + branch = this.getTopmostVia().getBranch(); // CANCEL uses same branch + } + else { + return; + } + via.setBranch( branch ); +} + +SIPResponse.prototype.getFirstLine =function(){ + if (this.statusLine == null) + { + return null; + } + else + { + return this.statusLine.encode(); + } +} + +SIPResponse.prototype.setSIPVersion =function(sipVersion){ + this.statusLine.setSipVersion(sipVersion); +} + +SIPResponse.prototype.getSIPVersion =function(){ + return this.statusLine.getSipVersion(); +} + +SIPResponse.prototype.toString =function(){ + if (this.statusLine == null) { + return ""; + } + else { + return this.statusLine.encode() + Object.getPrototypeOf(this).encode(); + } +} + +SIPResponse.prototype.createRequest =function(requestURI, via, cseq, from, to){ + var newRequest = new SIPRequest(); + var method = cseq.getMethod(); + var callid=this.getCallId(); + newRequest.setMethod(method); + newRequest.setRequestURI(requestURI); + this.setBranch(via, method); + newRequest.setHeader(via); + newRequest.setHeader(from); + newRequest.setHeader(to); + newRequest.setHeader(cseq); + newRequest.setHeader(callid); + newRequest.attachHeader(new MaxForwards(70), false); + var headerIterator = newRequest.getHeaders(); + for(var i=0;i0) { + this.host = hop.substring(0,colon); + var portstr; + if (slash>0) { + portstr = hop.substring(colon+1,slash); + this.transport = hop.substring(slash+1); + } else { + portstr = hop.substring(colon+1); + } + this.port = portstr-0; + } else { + if (slash>0) { + this.host = hop.substring(0,slash); + this.transport = hop.substring(slash+1); + this.port = 8080; + } else { + this.host = hop; + this.port = 8080; + } + } + if (this.host == null || this.host.length == 0) + { + console.error("HopImpl:HopImpl(): no host!"); + throw "HopImpl:HopImpl(): no host!"; + } + this.host = this.host.trim(); + this.transport = this.transport.trim(); + if ((brack>0) && this.host.charAt(0)!='[') { + console.error("HopImpl:HopImpl(): bad IPv6 reference spec"); + throw "HopImpl:HopImpl(): bad IPv6 reference spec"; + } + if ((this.transport.toLowerCase()!="tcp") && (this.transport.toLowerCase()!= "ws") ) { + console.error("HopImpl:HopImpl(): bad transport string " + this.transport); + throw "HopImpl:HopImpl(): bad transport string " + this.transport; + } + } + else if(arguments.length==3) + { + var hostName = arguments[0]; + var portNumber = arguments[1]; + var trans = arguments[2]; + this.host = hostName; + if(this.host.indexOf(":") >= 0) + { + if(this.host.indexOf("[") < 0) + { + this.host = "[" + this.host + "]"; + } + } + this.port = portNumber; + this.transport = trans; + } +} + +HopImpl.prototype.toString =function(){ + return this.host + ":" +this.port + "/" + this.transport; +} + +HopImpl.prototype.getHost =function(){ + return this.host; +} + +HopImpl.prototype.getPort =function(){ + return this.port; +} + +HopImpl.prototype.getTransport =function(){ + return this.transport; +} + +HopImpl.prototype.isURIRoute =function(){ + return this.uriRoute; +} + +HopImpl.prototype.setURIRouteFlag =function(){ + this.uriRoute=true; +} + +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SIPTransactionStack . + * @see gov/nist/javax/sip/stack/SIPTransactionStack.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + * + */ +function SIPTransactionStack() { + this.classname="SIPTransactionStack"; + + this.messageProcessors=new Array(); + this.sipMessageFactory=null; + this.activeClientTransactionCount = 0; + this.mergeTable=new Array(); + this.defaultRouter=null; + this.needsLogging=null; + this.stackName=null; + this.router=null; + this.maxConnections=-1; + this.useRouterForAll=null; + this.readTimeout= -1; + this.outboundProxy=null; + this.routerPath=null; + this.isAutomaticDialogSupportEnabled=null; + this.forkedEvents=new Array(); + this.generateTimeStampHeader=null; + this.cancelClientTransactionChecked = true; + this.remoteTagReassignmentAllowed = true; + this.logStackTraceOnMessageSend = true; + this.stackDoesCongestionControl = true; + this.checkBranchId=false; + this.isAutomaticDialogErrorHandlingEnabled = true; + this.isDialogTerminatedEventDeliveredForNullDialog = false; + this.serverTransactionTable=new Array(); + this.clientTransactionTable=new Array(); + this.terminatedServerTransactionsPendingAck=new Array(); + this.forkedClientTransactionTable=new Array(); + this.dialogTable=new Array(); + this.earlyDialogTable=new Array(); + this.pendingTransactions=new Array(); + this.unlimitedServerTransactionTableSize = true; + this.unlimitedClientTransactionTableSize = true; + this.serverTransactionTableHighwaterMark = 5000; + this.serverTransactionTableLowaterMark = 4000; + this.clientTransactionTableHiwaterMark = 1000; + this.clientTransactionTableLowaterMark = 800; + this.rfc2543Supported=true; + this.maxForkTime=0; + this.toExit=false; + this.isBackToBackUserAgent = false; + this.maxListenerResponseTime=-1; + this.non2XXAckPassedToListener=null; + this.maxMessageSize=null; + this.addressResolver = new DefaultAddressResolver(); + this.stackAddress =null; +} + +SIPTransactionStack.prototype.BASE_TIMER_INTERVAL=500; +SIPTransactionStack.prototype.CONNECTION_LINGER_TIME=8; +SIPTransactionStack.prototype.BRANCH_MAGIC_COOKIE_LOWER_CASE="z9hg4bk"; +SIPTransactionStack.prototype.TRYING=100; +SIPTransactionStack.prototype.RINGING=180; + +SIPTransactionStack.prototype.dialogCreatingMethods=new Array("REFER", "INVITE", "SUBSCRIBE", "REGISTER"); + +SIPTransactionStack.prototype.reInit =function(){ + this.messageProcessors = new Array(); + this.pendingTransactions = new Array(); + this.clientTransactionTable = new Array(); + this.serverTransactionTable = new Array(); + this.mergeTable = new Array(); + this.dialogTable = new Array(); + this.earlyDialogTable = new Array(); + this.terminatedServerTransactionsPendingAck = new Array(); + this.forkedClientTransactionTable = new Array(); + this.activeClientTransactionCount=0; +} + +SIPTransactionStack.prototype.addExtensionMethod =function(extensionMethod){ + if (extensionMethod!="NOTIFY") { + var l=null; + for(var i=0;i= 300 && statusCode <= 699 + && (this.getState() == null || + (cseqMethod==this.getMethod() && this.getState()== "EARLY"))) { + this.setState(this.TERMINATED_STATE); + } + if (this.getState() != "CONFIRMED" && this.getState() != "TERMINATED") { + if (this.originalRequest != null) { + var rrList = this.originalRequest.getRecordRouteHeaders(); + if (rrList != null) { + for(var i=rrList.length;i>=0;i--) + { + var rr = rrList[i]; + var route = this.routeList.getFirst(); + if (route != null && rr.getAddress()==route.getAddress()) { + this.routeList.removeFirst(); + } else { + break; + } + } + } + } + } + } else if (cseqMethod=="NOTIFY" + && (this.getMethod()=="SUBSCRIBE" || this.getMethod()=="REFER") && + sipResponse.getStatusCode() / 100 == 2 + && this.getState() == null) { + + this.setDialogId(sipResponse.getDialogId(true)); + this.sipStack.putDialog(this); + this.setState(this.CONFIRMED_STATE); + } else if (cseqMethod=="BYE" && statusCode / 100 == 2 + && this.isTerminatedOnBye()) { + this.setState(this.TERMINATED_STATE); + } + } + else { + if (cseqMethod=="BYE" && statusCode / 100 == 2 + && this.isTerminatedOnBye()) { + this.setState(this.TERMINATED_STATE); + } else { + var doPutDialog = false; + if (this.getLocalTag() == null && sipResponse.getTo().getTag() != null + && this.sipStack.isDialogCreated(cseqMethod) && cseqMethod==this.getMethod()) { + this.setLocalTag(sipResponse.getTo().getTag()); + doPutDialog = true; + } + if (statusCode / 100 != 2) { + if (statusCode / 100 == 1) { + if (doPutDialog) { + this.setState(this.EARLY_STATE); + this.setDialogId(sipResponse.getDialogId(true)); + this.sipStack.putDialog(this); + } + } + else { + if (!this.isReInvite() && this.getState() != this.CONFIRMED) { + this.setState(SIPDialog.TERMINATED_STATE); + } + } + } else { + if (this.dialogState <= this.EARLY_STATE && + (cseqMethod=="INVITE"|| cseqMethod=="SUBSCRIBE" || cseqMethod=="REFER")) { + this.setState(this.CONFIRMED_STATE); + } + if (doPutDialog) { + this.setDialogId(sipResponse.getDialogId(true)); + this.sipStack.putDialog(this); + } + } + } + } +} + +SIPDialog.prototype.getDialogId =function(){ + if (this.dialogId == null && this.lastResponse != null) { + this.dialogId = this.lastResponse.getDialogId(this.isServer()); + } + return this.dialogId; +} + +SIPDialog.prototype.isAssignedFunction =function(){ + return this.isAssigned; +} + +SIPDialog.prototype.setResponseTags =function(sipResponse){ + if (this.getLocalTag() != null || this.getRemoteTag() != null) { + return; + } + var responseFromTag = sipResponse.getFromTag(); + if (responseFromTag != null) { + if (responseFromTag==this.getLocalTag()) { + sipResponse.setToTag(this.getRemoteTag()); + } + else if (responseFromTag==this.getRemoteTag()) { + sipResponse.setToTag(this.getLocalTag()); + } + } +} + +SIPDialog.prototype.getSipProvider =function(){ + return this.sipProvider; +} + +SIPDialog.prototype.sendAck =function(request){ + var ackRequest = request; + if (!ackRequest.getMethod()=="ACK") { + console.error("SIPDialog:sendAck(): bad request method -- should be ACK"); + throw "SIPDialog:sendAck(): bad request method -- should be ACK"; + } + if (this.getState() == null || this.getState() == "EARLY") { + console.error("SIPDialog:sendAck(): bad dialog state " + this.getState()) + throw "SIPDialog:sendAck(): bad dialog state " + this.getState(); + } + + if (this.getCallId().getCallId()!=request.getCallId().getCallId()) { + console.error("SIPDialog:sendAck(): bad call ID in request"); + throw "SIPDialog:sendAck(): bad call ID in request"; + } + try { + if (this.getLocalTag() != null) { + ackRequest.getFrom().setTag(this.getLocalTag()); + } + if (this.getRemoteTag() != null) { + ackRequest.getTo().setTag(this.getRemoteTag()); + } + } catch (ex) { + console.error("SIPDialog:sendAck(): catched exception:"+ex); + throw "SIPDialog:sendAck(): catched exception:"+ex; + } + + var hop = this.sipStack.getNextHop(ackRequest); + if (hop == null) { + console.error("SIPDialog:sendAck(): no route!"); + throw "SIPDialog:sendAck(): no route!"; + } + var lp = this.sipProvider.getListeningPoint(hop.getTransport()); + if (lp == null) { + console.error("SIPDialog:sendAck(): no listening point for this provider registered at " + hop); + throw "SIPDialog:sendAck(): no listening point for this provider registered at " + hop; + } + var messageChannel = lp.getMessageProcessor().getMessageChannel(); + this.setLastAckSent(ackRequest); + messageChannel.sendMessage(ackRequest); + this.isAcknowledged = true; + this.highestSequenceNumberAcknowledged = Math.max(this.highestSequenceNumberAcknowledged, + ackRequest.getCSeq().getSeqNumber()); + if (this.dialogDeleteTask != null) { + //this.dialogDeleteTask.cancel(); + //this.dialogDeleteTask = null; + } + this.ackSeen = true; +} + +SIPDialog.prototype.getRemoteTag =function(){ + return this.hisTag; +} + +SIPDialog.prototype.isServer =function(){ + if (this.firstTransactionSeen == false) { + return this.serverTransactionFlag; + } + else { + return this.firstTransactionIsServerTransaction; + } +} + +SIPDialog.prototype.addTransaction =function(transaction){ + var sipRequest = transaction.getOriginalRequest(); + if (this.firstTransactionSeen && this.firstTransactionId!=(transaction.getBranchId()) + && transaction.getMethod()==this.firstTransactionMethod) { + this.reInviteFlag = true; + } + + if (this.firstTransactionSeen == false) { + this.storeFirstTransactionInfo(this, transaction); + if (sipRequest.getMethod()=="SUBSCRIBE") { + this.eventHeader = sipRequest.getHeader("Event"); + } + this.setLocalParty(sipRequest); + this.setRemoteParty(sipRequest); + this.setCallId(sipRequest); + if (this.originalRequest == null) { + this.originalRequest = sipRequest; + } + if (this.method == null) { + this.method = sipRequest.getMethod(); + } + if (transaction instanceof SIPServerTransaction) { + this.hisTag = sipRequest.getFrom().getTag(); + } else { + this.setLocalSequenceNumber(sipRequest.getCSeq().getSeqNumber()); + this.originalLocalSequenceNumber = this.localSequenceNumber; + this.myTag = sipRequest.getFrom().getTag(); + } + } + else if (transaction.getMethod()==this.firstTransactionMethod + && this.firstTransactionIsServerTransaction != transaction.isServerTransaction()) { + this.storeFirstTransactionInfo(this, transaction); + this.setLocalParty(sipRequest); + this.setRemoteParty(sipRequest); + this.setCallId(sipRequest); + this.originalRequest = sipRequest; + this.method = sipRequest.getMethod(); + } + if (transaction instanceof SIPServerTransaction) { + this.setRemoteSequenceNumber(sipRequest.getCSeq().getSeqNumber()); + } + this.lastTransaction = transaction; +} + +SIPDialog.prototype.storeFirstTransactionInfo =function(dialog,transaction){ + dialog.firstTransaction = transaction; + dialog.firstTransactionSeen = true; + dialog.firstTransactionIsServerTransaction = transaction.isServerTransaction(); + dialog.firstTransactionSecure = true; + //dialog.firstTransactionPort = transaction.getPort(); + dialog.firstTransactionId = transaction.getBranchId(); + dialog.firstTransactionMethod = transaction.getMethod(); + if (dialog.isServer()) { + var st = transaction; + var response = st.getLastResponse(); + dialog.contactHeader = response != null ? response.getContactHeader() : null; + } else { + var ct = transaction; + if (ct != null) { + var sipRequest = ct.getOriginalRequest(); + dialog.contactHeader = sipRequest.getContactHeader(); + } + } +} + +SIPDialog.prototype.setLocalParty =function(sipMessage){ + if (!this.isServer()) { + this.localParty = sipMessage.getFrom().getAddress(); + } else { + this.localParty = sipMessage.getTo().getAddress(); + } +} + +SIPDialog.prototype.setRemoteParty =function(sipMessage){ + if (!this.isServer()) { + this.remoteParty = sipMessage.getTo().getAddress(); + } else { + this.remoteParty = sipMessage.getFrom().getAddress(); + } +} + +SIPDialog.prototype.setCallId =function(sipRequest){ + this.callIdHeader = sipRequest.getCallId(); +} + +SIPDialog.prototype.setLocalSequenceNumber =function(lCseq){ + this.localSequenceNumber = lCseq; +} + +SIPDialog.prototype.getLocalParty =function(){ + return this.localParty; +} + +SIPDialog.prototype.getRemoteParty =function(){ + return this.remoteParty; +} + +SIPDialog.prototype.addEventListener =function(newListener){ + var l=null; + for(var i=0;i=0;i--) + { + var rr = recordRouteList.getHeaderList()[i]; + var route = new Route(); + route.setAddress(rr.getAddress()); + route.setParameters(rr.getParameters()); + this.routeList.add(route); + } + } + else { + this.routeList = new RouteList(); + for(i=0;i 0) { + via.setParameters(originalRequestParameters.clone()); + } + } + via.setBranch(Utils.prototype.generateBranchId()); // new branch + vias.add(via); + sipRequest.setVia(vias); + var from = new From(); + from.setAddress(this.localParty); + from.setTag(this.myTag); + sipRequest.setFrom(from); + var to = new To(); + to.setAddress(this.remoteParty); + if (this.hisTag != null) { + to.setTag(this.hisTag); + } + sipRequest.setTo(to); + sipRequest.setMaxForwards(new MaxForwards(70)); + + if (this.originalRequest != null) { + var authorization = this.originalRequest.getAuthorization(); + if (authorization != null) { + sipRequest.setHeader(authorization); + } + } + this.updateRequest(sipRequest); + return sipRequest; + } catch (ex) { + console.error("SIPDialog:createAck(): catched unexpected exception ", ex); + throw "SIPDialog:createAck(): catched unexpected exception"; + } +} + +SIPDialog.prototype.setSipProvider =function(sipProvider){ + this.sipProvider = sipProvider; +} + +SIPDialog.prototype.doTargetRefresh =function(sipMessage){ + var contactList = sipMessage.getContactHeaders(); + if (contactList != null) { + var contact = contactList.getFirst(); + this.setRemoteTarget(contact); + } +} + +SIPDialog.prototype.createReliableProvisionalResponse =function(statusCode){ + if (!(this.firstTransactionIsServerTransaction)) { + console.error("SIPDialog:createReliableProvisionalResponse(): not a Server Dialog!"); + throw "SIPDialog:createReliableProvisionalResponse(): not a Server Dialog!"; + } + if (statusCode <= 100 || statusCode > 199) { + console.error("SIPDialog:createReliableProvisionalResponse(): bad status code "); + throw "SIPDialog:createReliableProvisionalResponse(): bad status code "; + } + var request = this.originalRequest; + if (request.getMethod()!="INVITE") { + console.error("SIPDialog:createReliableProvisionalResponse(): bad method"); + throw "SIPDialog:createReliableProvisionalResponse(): bad method"; + } + var list = request.getHeaders("Supported"); + if (list == null&&!optionPresent(list, "100rel")) { + list = request.getHeaders("Require"); + if (list == null&&!optionPresent(list, "100rel")) { + console.error("SIPDialog:createReliableProvisionalResponse(): no Supported/Require 100rel header in the request"); + throw "SIPDialog:createReliableProvisionalResponse(): no Supported/Require 100rel header in the request"; + } + } + var response = request.createResponse(statusCode); + var require = new Require(); + require.setOptionTag("100rel"); + response.addHeader(require); + var rseq = new RSeq(); + rseq.setSeqNumber("1L"); + var rrl = request.getRecordRouteHeaders(); + if (rrl != null) { + var rrlclone = rrl; + response.setHeader(rrlclone); + } + return response; +} + +SIPDialog.prototype.sendReliableProvisionalResponse =function(relResponse){ + if (!this.isServer()) { + console.error("SIPDialog:sendReliableProvisionalResponse(): not a Server Dialog!"); + throw "SIPDialog:sendReliableProvisionalResponse(): not a Server Dialog!"; + } + var sipResponse = relResponse; + if (relResponse.getStatusCode() == 100) { + console.error("SIPDialog:sendReliableProvisionalResponse(): cannot send 100 as a reliable provisional response"); + throw "SIPDialog:sendReliableProvisionalResponse(): cannot send 100 as a reliable provisional response"; + } + if (relResponse.getStatusCode() / 100 > 2) { + console.error("SIPDialog:sendReliableProvisionalResponse(): response code is not a 1xx response - should be in the range 101 to 199 "); + throw "SIPDialog:sendReliableProvisionalResponse(): response code is not a 1xx response - should be in the range 101 to 199 "; + } + if (sipResponse.getToTag() == null) { + console.error("SIPDialog:sendReliableProvisionalResponse(): badly formatted response -- To tag mandatory for Reliable Provisional Response"); + throw "SIPDialog:sendReliableProvisionalResponse(): badly formatted response -- To tag mandatory for Reliable Provisional Response"; + } + var requireList = relResponse.getHeaders("Require"); + var found = false; + if (requireList != null) { + for(var i=0;i MAX_VALUE || hash < MIN_VALUE) + { + hash &= 0xFFFFFFFF; + } + } + } + return hash; + } +} + +SIPTransaction.prototype.getViaPort =function(){ + return this.getViaHeader().getPort(); +} + +SIPTransaction.prototype.getPort =function(){ + return this.encapsulatedChannel.getPort(); +} + +SIPTransaction.prototype.doesCancelMatchTransaction =function(requestToTest){ + var viaHeaders; + var topViaHeader; + var messageBranch; + var transactionMatches = false; + if (this.getOriginalRequest() == null || this.getMethod()=="CANCEL") + { + return false; + } + viaHeaders = requestToTest.getViaHeaders(); + if (viaHeaders != null) { + topViaHeader = viaHeaders.getFirst(); + messageBranch = topViaHeader.getBranch(); + if (messageBranch != null) { + if(messageBranch.toLowerCase().substring(0,7)!="z9hg4bk") + { + messageBranch = null; + } + } + if (messageBranch != null && this.getBranch() != null) { + if ((this.getBranch().toLowerCase()==messageBranch.toLowerCase()) + && (topViaHeader.getSentBy().equals(this.getOriginalRequest().getViaHeaders().getFirst().getSentBy()))) { + transactionMatches = true; + } + } else { + if (this.getOriginalRequest().getRequestURI()==requestToTest.getRequestURI() + && this.getOriginalRequest().getTo()==requestToTest.getTo() + && this.getOriginalRequest().getFrom()==requestToTest.getFrom() + && this.getOriginalRequest().getCallId().getCallId()==requestToTest.getCallId().getCallId() + && this.getOriginalRequest().getCSeq().getSeqNumber() == requestToTest.getCSeq().getSeqNumber() + && topViaHeader==this.getOriginalRequest().getViaHeaders().getFirst()) { + transactionMatches = true; + } + } + } + if (transactionMatches) { + this.setPassToListener(); + } + return transactionMatches; +} + +SIPTransaction.prototype.close =function(){ + this.encapsulatedChannel.close(); +} + +SIPTransaction.prototype.isSecure =function(){ + return this.encapsulatedChannel.isSecure(); +} + +SIPTransaction.prototype.getMessageProcessor =function(){ + return this.encapsulatedChannel.getMessageProcessor(); +} + +SIPTransaction.prototype.setApplicationData =function(applicationData){ + this.applicationData = applicationData; +} + +SIPTransaction.prototype.getApplicationData =function(){ + return this.applicationData; +} + +SIPTransaction.prototype.setEncapsulatedChannel =function(messageChannel){ + this.encapsulatedChannel = messageChannel; +} + +SIPTransaction.prototype.getSipProvider =function(){ + return this.getMessageProcessor().getListeningPoint().getProvider(); +} + +SIPTransaction.prototype.raiseIOExceptionEvent =function(){ + this.setState("TERMINATED"); +} + +SIPTransaction.prototype.passToListener =function(){ + return this.toListener; +} + +SIPTransaction.prototype.setPassToListener =function(){ + this.toListener = true; +} + +SIPTransaction.prototype.testAndSetTransactionTerminatedEvent =function(){ + var retval=!this.terminatedEventDelivered; + this.terminatedEventDelivered = true; + return retval; +} + +SIPTransaction.prototype.startTransactionTimer =function(){ + +} + +SIPTransaction.prototype.isMessagePartOfTransaction =function(){ + +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SIPClientTransaction . + * @see gov/nist/javax/sip/stack/SIPClientTransaction.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @author Jean Deruelle (jean.deruelle@telestax.com) + * @version 1.0 + * + */ + +function SIPClientTransaction(newSIPStack,newChannelToUse) { + this.classname="SIPClientTransaction"; + this.encapsulatedChannel=newChannelToUse; + this.wsurl=this.encapsulatedChannel.wsurl; + /*if (this.isReliable()) { + this.encapsulatedChannel.useCount++; + }*/ + this.disableTimeoutTimer(); + this.sipStack=newSIPStack; + this.infoApp=newSIPStack.infoApp; + this.addEventListener(newSIPStack); + this.originalRequest=null; + this.setBranch(Utils.prototype.generateBranchId()); + this.notifyOnRetransmit = false; + this.timeoutIfStillInCallingState = false; + this.setEncapsulatedChannel(newChannelToUse); + this.messageProcessor = newChannelToUse.messageProcessor; + this.sipDialogs=new Array(); + this.lastRequest=null; + this.viaPort=null; + this.viaHost=null; + this.respondTo=null; + this.defaultDialog=null; + this.nextHop=null; + this.callingStateTimeoutCount=null; + this.timer=null; + this.oldmessage=null; +} + +SIPClientTransaction.prototype = new SIPTransaction(); +SIPClientTransaction.prototype.constructor=SIPClientTransaction; +SIPClientTransaction.prototype.BRANCH_MAGIC_COOKIE_LOWER_CASE="z9hg4bk"; +SIPClientTransaction.prototype.MAXIMUM_RETRANSMISSION_TICK_COUNT=8; +SIPClientTransaction.prototype.COMPLETED="COMPLETED"; +SIPClientTransaction.prototype.PROCEEDING="PROCEEDING"; +SIPClientTransaction.prototype.CALLING="CALLING"; +SIPClientTransaction.prototype.TERMINATED="TERMINATED"; +SIPClientTransaction.prototype.ACK="ACK"; +SIPClientTransaction.prototype.INVITE="INVITE"; +SIPClientTransaction.prototype.TRYING="TRYING"; +SIPClientTransaction.prototype.CANCEL="CANCEL"; +SIPClientTransaction.prototype.BYE="BYE"; +SIPClientTransaction.prototype.SUBSCRIBE="SUBSCRIBE"; +SIPClientTransaction.prototype.NOTIFY="NOTIFY"; +SIPClientTransaction.prototype.TIMER_B=64; +SIPClientTransaction.prototype.TIMER_D=SIPTransaction.prototype.TIMER_D; +SIPClientTransaction.prototype.TIMER_F=64; +SIPClientTransaction.prototype.TIMER_K=SIPTransaction.prototype.T4; +SIPClientTransaction.prototype.TIMER_J=64; +SIPClientTransaction.prototype.TimeStampHeader="Timestamp"; +SIPClientTransaction.prototype.RouteHeader="Route"; +SIPClientTransaction.prototype.RETRANSMIT="RETRANSMIT"; +SIPClientTransaction.prototype.TRANSPORT_ERROR=2; +SIPClientTransaction.prototype.TIMEOUT_ERROR=1; +SIPClientTransaction.prototype.EARLY="EARLY"; +SIPClientTransaction.prototype.CONNECTION_LINGER_TIME=8; +SIPClientTransaction.prototype.BASE_TIMER_INTERVAL=500; + +SIPClientTransaction.prototype.setResponseInterface =function(newRespondTo){ + this.respondTo = newRespondTo; +} + +SIPClientTransaction.prototype.getRequestChannel =function(){ + return this; +} + +SIPClientTransaction.prototype.isMessagePartOfTransaction =function(messageToTest){ + var viaHeaders = messageToTest.getViaHeaders(); + var transactionMatches= false; + var messageBranch = viaHeaders.getFirst().getBranch(); + if(this.getBranch() != null && messageBranch != null + && this.getBranch().toLowerCase().substring(0,7)==(this.BRANCH_MAGIC_COOKIE_LOWER_CASE) + && messageBranch.toLowerCase().substring(0,7)==(this.BRANCH_MAGIC_COOKIE_LOWER_CASE)) + { + var rfc3261Compliant = true; + } + else + { + rfc3261Compliant = false; + } + if (this.COMPLETED == this.getState()) { + if (rfc3261Compliant) { + if(this.getBranch()().toLowerCase()==viaHeaders.getFirst().getBranch().toLowerCase() + && this.getMethod()==messageToTest.getCSeq().getMethod()) + { + transactionMatches = true; + } + else + { + transactionMatches=false; + } + } + else { + if(this.getBranch()==messageToTest.getTransactionId()) + { + transactionMatches = true; + } + else + { + transactionMatches=false; + } + } + } + else if (!this.isTerminated()) { + if (rfc3261Compliant) { + if (viaHeaders != null) { + if (this.getBranch().toLowerCase()==viaHeaders.getFirst().getBranch().toLowerCase()) { + if(this.getOriginalRequest().getCSeq().getMethod()==messageToTest.getCSeq().getMethod()) + { + transactionMatches = true; + } + else + { + transactionMatches=false; + } + } + } + } + else { + if (this.getBranch() != null) { + if(this.getBranch().toLowerCase()==messageToTest.getTransactionId().toLowerCase()) + { + transactionMatches = true; + } + else + { + transactionMatches=false; + } + } else { + if(this.getOriginalRequest().getTransactionId().toLowerCase()==messageToTest.getTransactionId().toLowerCase()) + { + transactionMatches = true; + } + else + { + transactionMatches=false; + } + } + } + } + return transactionMatches; +} + +SIPClientTransaction.prototype.sendMessage =function(messageToSend){ + var transactionRequest = messageToSend; + var topVia = transactionRequest.getViaHeaders().getFirst(); + topVia.setBranch(this.getBranch()); + if (this.PROCEEDING == this.getState()|| this.CALLING == this.getState()) { + if (transactionRequest.getMethod()==this.ACK) { + if (this.isReliable()) { + this.setState(this.TERMINATED); + } else { + this.setState(this.COMPLETED); + } + SIPTransaction.prototype.sendMessage.call(this,transactionRequest); + return; + } + } + try { + this.lastRequest = transactionRequest; + if (this.getState() == null) { + this.setOriginalRequest(transactionRequest); + if (transactionRequest.getMethod()==this.INVITE) { + this.setState(this.CALLING); + } + else if (transactionRequest.getMethod()==this.ACK) { + this.setState(this.TERMINATED); + } + else { + this.setState(this.TRYING); + } + if (this.isInviteTransaction()) { + this.enableTimeoutTimer(this.TIMER_B); + } + else { + this.enableTimeoutTimer(this.TIMER_F); + } + } + SIPTransaction.prototype.sendMessage.call(this,transactionRequest); + } catch (ex) { + console.error("SIPClientTransaction:sendMessage(): catched exception:"+ex); + this.setState(this.TERMINATED); + } + this.isMapped = true; + this.startTransactionTimer(); +} + +SIPClientTransaction.prototype.processResponse =function(){ + if(arguments.length==2) + { + var sipResponse=arguments[0]; + var incomingChannel=arguments[1]; + this.processResponseargu2(sipResponse, incomingChannel); + } + else + { + var transactionResponse=arguments[0]; + var sourceChannel=arguments[1]; + var dialog=arguments[2]; + this.processResponseargu3(transactionResponse, sourceChannel, dialog); + } +} + +SIPClientTransaction.prototype.processResponseargu2 =function(sipResponse,incomingChannel){ + var dialog = null; + var method = sipResponse.getCSeq().getMethod(); + var dialogId = sipResponse.getDialogId(false); + if (method==this.CANCEL && this.lastRequest != null) { + var ict = this.lastRequest.getInviteTransaction(); + if (ict != null) { + dialog = ict.defaultDialog; + } + } else { + dialog = this.getDialog(dialogId); + } + if (dialog == null) { + var code = sipResponse.getStatusCode(); + if ((code > 100 && code < 300) + && (sipResponse.getToTag() != null || this.sipStack.isRfc2543Supported()) + && this.sipStack.isDialogCreated(method)) { + if (this.defaultDialog != null) { + if (sipResponse.getFromTag() != null) { + var dialogResponse = this.defaultDialog.getLastResponse(); + var defaultDialogId = this.defaultDialog.getDialogId(); + if (dialogResponse == null|| method==this.SUBSCRIBE && defaultDialogId==dialogId + && dialogResponse.getCSeq().getMethod()==this.NOTIFY) { + this.defaultDialog.setLastResponse(this, sipResponse); + dialog = this.defaultDialog; + } else { + dialog = this.sipStack.getDialog(dialogId); + if (dialog == null) { + if (this.defaultDialog.isAssignedFunction()) { + dialog = this.sipStack.createDialog(this, sipResponse); + } + } + } + if ( dialog != null ) { + this.setDialog(dialog, dialog.getDialogId()); + } + } else { + console.error("SIPClientTransaction:processResponseargu2(): response without from-tag " + sipResponse); + throw "SIPClientTransaction:processResponseargu2(): response without from-tag " + sipResponse; + } + } else { + if (this.sipStack.isAutomaticDialogSupportEnabled) { + dialog = this.sipStack.createDialog(this, sipResponse); + this.setDialog(dialog, dialog.getDialogId()); + } + } + } else { + dialog = this.defaultDialog; + } + } else { + dialog.setLastResponse(this, sipResponse); + } + this.processResponse(sipResponse, incomingChannel, dialog); +} + +SIPClientTransaction.prototype.processResponseargu3 =function(transactionResponse,sourceChannel,dialog){ + + if (this.getState() == null) + { + return; + } + if ((this.COMPLETED == this.getState() || this.TERMINATED == this.getState()) + && transactionResponse.getStatusCode() / 100 == 1) { + return; + } + this.lastResponse = transactionResponse; + try { + if (this.isInviteTransaction()) + { + this.inviteClientTransaction(transactionResponse, sourceChannel, dialog); + } + else + { + this.nonInviteClientTransaction(transactionResponse, sourceChannel, dialog); + } + } catch (ex) { + console.error("SIPClientTransaction:processResponseargu3(): catched exception:"+ex); + this.setState(this.TERMINATED); + } +} + +SIPClientTransaction.prototype.nonInviteClientTransaction =function(transactionResponse,sourceChannel,sipDialog){ + + var statusCode = transactionResponse.getStatusCode(); + if (this.TRYING == this.getState()) { + if (100 <= statusCode && statusCode <= 199) { + this.setState(this.PROCEEDING); + this.enableTimeoutTimer(this.TIMER_F); + if (this.respondTo != null) { + this.respondTo.processResponse(transactionResponse, this, sipDialog); + } + } + else if (200 <= statusCode && statusCode <= 699) { + if (!this.isReliable()) { + this.setState(this.COMPLETED); + this.enableTimeoutTimer(this.TIMER_K); + } + else { + this.setState(this.TERMINATED); + this.sipStack.removeTransaction(this); + clearInterval(this.timer); + } + if (this.respondTo != null) { + this.respondTo.processResponse(transactionResponse, this, sipDialog); + } + } + } + else if (this.PROCEEDING == this.getState()) { + if (100 <= statusCode && statusCode <= 199) { + if (this.respondTo != null) { + this.respondTo.processResponse(transactionResponse, this, sipDialog); + } + } else if (200 <= statusCode && statusCode <= 699) { + if (this.respondTo != null) { + this.respondTo.processResponse(transactionResponse, this, sipDialog); + } + this.disableTimeoutTimer(); + if (!this.isReliable()) { + this.setState(this.COMPLETED); + this.enableTimeoutTimer(this.TIMER_K); + } else { + this.setState(this.TERMINATED); + } + } + } +} + +SIPClientTransaction.prototype.inviteClientTransaction =function(transactionResponse,sourceChannel,dialog){ + var statusCode = transactionResponse.getStatusCode(); + if (this.TERMINATED == this.getState()) { + var ackAlreadySent = false; + if(dialog != null) { + } + if (dialog != null && dialog.isAckSeen() && dialog.getLastAckSent() != null) { + if (dialog.getLastAckSent().getCSeq().getSeqNumber() == transactionResponse.getCSeq().getSeqNumber() + && transactionResponse.getFromTag()==dialog.getLastAckSent().getFromTag()) { + ackAlreadySent = true; + } + } + if (dialog!= null && !ackAlreadySent + && transactionResponse.getCSeq().getMethod()==dialog.getMethod()) { + dialog.resendAck(); + } + this.sipStack.removeTransaction(this); + clearInterval(this.timer); + return; + } + else if (this.CALLING == this.getState()) { + if (200 <= statusCode && statusCode <= 299) { + this.disableTimeoutTimer(); + this.setState(this.TERMINATED); + if (this.respondTo != null) + { + this.respondTo.processResponse(transactionResponse, this, dialog); + } + } + else if (100 <= statusCode && statusCode <= 199) { + this.disableTimeoutTimer(); + this.setState(this.PROCEEDING); + if (this.respondTo != null) + { + this.respondTo.processResponse(transactionResponse, this, dialog); + } + } + else if (300 <= statusCode && statusCode <= 699) { + this.sendMessage(this.createErrorAck()); + if (this.respondTo != null) + { + this.respondTo.processResponse(transactionResponse, this, dialog); + } + if (!this.isReliable()) { + this.setState(this.COMPLETED); + this.enableTimeoutTimer(this.TIMER_D); + } + else { + this.setState(this.TERMINATED); + } + } + } + else if (this.PROCEEDING == this.getState()) { + if (100 <= statusCode && statusCode <= 199) + { + if (this.respondTo != null) { + this.respondTo.processResponse(transactionResponse, this, dialog); + } + } + else if (200 <= statusCode && statusCode <= 299) + { + this.setState(this.TERMINATED); + this.sipStack.removeTransaction(this); + clearInterval(this.timer); + if (this.respondTo != null) { + this.respondTo.processResponse(transactionResponse, this, dialog); + } + } + else if (300 <= statusCode && statusCode <= 699) + { + this.sendMessage(this.createErrorAck()); + if (!this.isReliable()) { + this.setState(this.COMPLETED); + this.enableTimeoutTimer(this.TIMER_D); + } + else + { + this.setState(this.TERMINATED); + this.sipStack.removeTransaction(this); + clearInterval(this.timer); + } + if (this.respondTo != null) + { + this.respondTo.processResponse(transactionResponse, this, dialog); + } + } + } + else if (this.COMPLETED == this.getState()) { + this.setState(this.TERMINATED); + this.sipStack.removeTransaction(this); + clearInterval(this.timer); + if (300 <= statusCode && statusCode <= 699) { + this.sendMessage(this.createErrorAck()); + } + } +} + +SIPClientTransaction.prototype.sendRequest =function(){ + var sipRequest = this.getOriginalRequest(); + if (this.getState() != null) + { + console.error("SIPClientTransaction:sendRequest(): request already sent"); + throw "SIPClientTransaction:sendRequest(): request already sent"; + } + try { + sipRequest.checkHeaders(); + } catch (ex) { + console.error("SIPClientTransaction:sendRequest(): "+ ex); + throw "SIPClientTransaction:sendRequest(): "+ex; + } + try { + if (this.getMethod()==this.CANCEL + && this.sipStack.isCancelClientTransactionChecked()) { + var ct = this.sipStack.findCancelTransaction(this.getOriginalRequest(), false); + if (ct == null) { + console.error("SIPClientTransaction:sendRequest(): could not find original tx to cancel. RFC 3261 9.1"); + throw "SIPClientTransaction:sendRequest(): could not find original tx to cancel. RFC 3261 9.1"; + } + else if (ct.getState() == null) { + console.error("SIPClientTransaction:sendRequest(): state is null no provisional response yet -- cannot cancel RFC 3261 9.1"); + throw "SIPClientTransaction:sendRequest(): state is null no provisional response yet -- cannot cancel RFC 3261 9.1"; + } + else if (ct.getMethod()!=this.INVITE) { + console.error("SIPClientTransaction:sendRequest(): cannot cancel non-invite requests RFC 3261 9.1"); + throw "SIPClientTransaction:sendRequest(): cannot cancel non-invite requests RFC 3261 9.1"; + } + } + else if (this.getMethod()==this.BYE + ||this.getMethod()==this.NOTIFY) { + var dialog = this.sipStack.getDialog(this.getOriginalRequest().getDialogId(false)); + if (this.getSipProvider().isAutomaticDialogSupportEnabled() && dialog != null) { + console.error("SIPClientTransaction:sendRequest(): Dialog is present and AutomaticDialogSupport is enabled for the provider -- Send the Request using the Dialog.sendRequest(transaction)"); + throw "SIPClientTransaction:sendRequest(): Dialog is present and AutomaticDialogSupport is enabled for the provider -- Send the Request using the Dialog.sendRequest(transaction)"; + } + } + if (this.getMethod()==this.INVITE) { + dialog = this.getDefaultDialog(); + } + this.isMapped = true; + this.sendMessage(sipRequest); + } catch (ex) { + this.setState(this.TERMINATED); + console.error("SIPClientTransaction:sendRequest(): catched exception:"+ ex); + throw "SIPClientTransaction:sendRequest(): catched exception:"+ ex; + } +} + +SIPClientTransaction.prototype.fireTimeoutTimer =function(){ + clearInterval(this.timer); + var dialog = this.getDialog(); + if (this.CALLING == this.getState()|| this.TRYING == this.getState() + || this.PROCEEDING == this.getState()) { + if (dialog != null&& (dialog.getState() == null || dialog.getState() == this.EARLY)) { + if (this.getSIPStack().isDialogCreated(this.getOriginalRequest().getMethod())) { + dialog.delet(); + } + } + else if (dialog != null) { + if (this.getOriginalRequest().getMethod().toLowerCase()==this.BYE.toLowerCase() + && dialog.isTerminatedOnBye()) { + dialog.delet(); + } + } + } + if (this.COMPLETED != this.getState()) { + this.raiseErrorEvent(this.TIMEOUT_ERROR); + if (this.getOriginalRequest().getMethod().toLowerCase()==this.CANCEL.toLowerCase()) { + var inviteTx = this.getOriginalRequest().getInviteTransaction(); + if (inviteTx != null&& inviteTx.getDialog() != null + && (inviteTx.getState() == this.CALLING || inviteTx.getState() == this.PROCEEDING)) + { + inviteTx.setState(this.TERMINATED); + } + } + } + else { + this.setState(this.TERMINATED); + } +} + +SIPClientTransaction.prototype.createCancel =function(){ + var originalRequest = this.getOriginalRequest(); + if (originalRequest == null) + { + console.error("SIPClientTransaction:createCancel(): bad state " + this.getState()); + throw "SIPClientTransaction:createCancel(): bad state " + this.getState(); + } + if (originalRequest.getMethod()!=this.INVITE) + { + console.error("SIPClientTransaction:createCancel(): only INIVTE may be cancelled"); + throw "SIPClientTransaction:createCancel(): only INIVTE may be cancelled"; + } + if (originalRequest.getMethod().toLowerCase()==this.ACK.toLowerCase()) + { + console.error("SIPClientTransaction:createCancel(): cannot Cancel ACK!"); + throw "SIPClientTransaction:createCancel(): cannot Cancel ACK!"; + } + else { + var cancelRequest = originalRequest.createCancelRequest(); + cancelRequest.setInviteTransaction(this); + return cancelRequest; + } +} + +SIPClientTransaction.prototype.createAck =function(){ + var originalRequest = this.getOriginalRequest(); + if (originalRequest == null) + { + console.error("SIPClientTransaction:createAck(): bad state " + getState()); + throw "SIPClientTransaction:createAck(): bad state " + getState(); + } + + if (this.getMethod().toLowerCase()==this.ACK.toLowerCase()) { + console.error("SIPClientTransaction:createAck(): cannot ACK an ACK!"); + throw "SIPClientTransaction:createAck(): cannot ACK an ACK!"; + } else if (this.lastResponse == null) { + console.error("SIPClientTransaction:createAck(): bad Transaction state"); + throw "SIPClientTransaction:createAck(): bad Transaction state"; + } else if (this.lastResponse.getStatusCode() < 200) { + console.error("SIPClientTransaction:createAck() : cannot ACK a provisional response!"); + throw "SIPClientTransaction:createAck(): cannot ACK a provisional response!"; + } + var ackRequest = originalRequest.createAckRequest(this.lastResponse.getTo()); + var recordRouteList = this.lastResponse.getRecordRouteHeaders(); + if (recordRouteList == null) { + if (this.lastResponse.getContactHeaders() != null + && this.lastResponse.getStatusCode() / 100 != 3) { + var contact = this.lastResponse.getContactHeaders().getFirst(); + var uri = contact.getAddress().getURI(); + ackRequest.setRequestURI(uri); + } + return ackRequest; + } + ackRequest.removeHeader(this.RouteHeader); + var routeList = new RouteList(); + for(var i=recordRouteList.getHeaderList().length-1;i>=0;i--) + { + var rr = recordRouteList.getHeaderList()[i]; + var route = new Route(); + route.setAddress(rr.getAddress()); + route.setParameters(rr.getParameters()); + routeList.add(route); + } + contact = null; + if (this.lastResponse.getContactHeaders() != null) { + contact = this.lastResponse.getContactHeaders().getFirst(); + } + if (!routeList.getFirst().getAddress().getURI().hasLrParam()) { + route = null; + if (contact != null) { + route = new Route(); + route.setAddress(contact.getAddress()); + } + var firstRoute = routeList.getFirst(); + routeList.removeFirst(); + uri = firstRoute.getAddress().getURI(); + ackRequest.setRequestURI(uri); + if (route != null) + routeList.add(route); + ackRequest.addHeader(routeList); + } + else { + if (contact != null) { + uri = contact.getAddress().getURI(); + ackRequest.setRequestURI(uri); + ackRequest.addHeader(routeList); + } + } + return ackRequest; +} + +SIPClientTransaction.prototype.createErrorAck =function(){ + var originalRequest = this.getOriginalRequest(); + if (originalRequest == null) + { + console.error("SIPClientTransaction:createErrorAck(): bad state " + getState()); + throw "SIPClientTransaction:createErrorAck(): bad state " + getState(); + } + if (this.getMethod()!=this.INVITE) + { + console.error("SIPClientTransaction:createErrorAck(): can only ACK an INVITE!"); + throw "SIPClientTransaction:createErrorAck(): can only ACK an INVITE!"; + } + else if (this.lastResponse == null) + { + console.error("SIPClientTransaction:createErrorAck(): bad Transaction state"); + throw "SIPClientTransaction:createErrorAck(): bad Transaction state"; + } + else if (this.lastResponse.getStatusCode() < 200) + { + console.error("SIPClientTransaction:createErrorAck(): cannot ACK a provisional response!"); + throw "SIPClientTransaction:createErrorAck(): cannot ACK a provisional response!"; + } + return originalRequest.createErrorAck(this.lastResponse.getTo()); +} + +SIPClientTransaction.prototype.setViaPort =function(port){ + this.viaPort = port; +} + +SIPClientTransaction.prototype.setViaHost =function(host){ + this.viaHost = host; +} + +SIPClientTransaction.prototype.getViaPort =function(){ + return this.viaPort; +} + +SIPClientTransaction.prototype.getViaHost =function(){ + return this.viaHost; +} + +SIPClientTransaction.prototype.getOutgoingViaHeader =function(){ + return this.getMessageProcessor().getViaHeader(); +} + +SIPClientTransaction.prototype.clearState =function(){ + +} + +SIPClientTransaction.prototype.setState =function(newState){ + if (newState == this.TERMINATED && this.isReliable()) { + this.collectionTime = this.TIMER_J; + } + /*if (SIPTransaction.prototype.getState.call(this) != this.COMPLETED + && (newState == this.COMPLETED || newState == this.TERMINATED)) { + this.sipStack.decrementActiveClientTransactionCount(); + }*/ + SIPTransaction.prototype.setState.call(this,newState); +} + +SIPClientTransaction.prototype.startTransactionTimer =function(){ + if (this.transactionTimerStarted==false) { + this.transactionTimerStarted=true; + if (this.timer == null ) { + var that=this; + this.timer=setInterval(function(){ + if(that.isTerminated()) + { + var sipStack=that.getSIPStack(); + sipStack.removeTransaction(that); + } + else + { + that.fireTimer(); + } + },this.BASE_TIMER_INTERVAL); + } + } +} + +SIPClientTransaction.prototype.terminate =function(){ + this.setState(this.TERMINATED); +} + +SIPClientTransaction.prototype.checkFromTag =function(sipResponse){ + var originalFromTag = this.getRequest().getFromTag(); + var sipResponseFromTag = sipResponse.getFrom().getTag(); + if (this.defaultDialog != null) { + // Added for https://code.google.com/p/webrtcomm/issues/detail?id=19 as XOR below is not enough + if (originalFromTag == null && sipResponseFromTag == null) { + return false; + } + if (originalFromTag == null ^ sipResponseFromTag == null) { + return false; + } + if (originalFromTag != null && sipResponseFromTag != null && + originalFromTag.toLowerCase() != sipResponseFromTag.toLowerCase()) { + return false; + } + } + return true; +} + +SIPClientTransaction.prototype.getDialog =function(){ + if(arguments.length==0) + { + return this.getDialogargu0(); + } + else if(arguments.length==1) + { + var dialogId=arguments[0]; + return this.getDialogargu1(dialogId); + } +} + +SIPClientTransaction.prototype.getDialogargu0 =function(){ + var retval = null; + if (this.lastResponse != null && this.lastResponse.getFromTag() != null + && this.lastResponse.getToTag() != null + && this.lastResponse.getStatusCode() != 100) { + var dialogId = this.lastResponse.getDialogId(false); + retval = this.getDialog(dialogId); + } + if (retval == null) { + retval = this.defaultDialog; + } + return retval; +} + +SIPClientTransaction.prototype.getDialogargu1 =function(dialogId){ + var retval=null; + for(var i=0;i=100) { + this.setState("PROCEEDING"); + } + else if (200 <= statusCode && statusCode <= 699) { + if (!this.isInviteTransaction()) { + if (!this.isReliable()) { + this.setState("COMPLETED"); + this.enableTimeoutTimer(this.TIMER_J); + } + else { + this.setState("TERMINATED"); + } + } + else { + if (statusCode <=299 && statusCode>=200) { + this.disableTimeoutTimer(); + this.collectionTime = this.TIMER_J; + this.setState("TERMINATED"); + } + else { + this.setState("COMPLETED"); + this.enableTimeoutTimer(this.TIMER_H); + } + } + } + } + else if (this.getRealState() == "PROCEEDING") { + if (this.isInviteTransaction()) { + if (statusCode <=299 && statusCode>=100) { + this.disableTimeoutTimer(); + this.collectionTime = this.TIMER_J; + this.setState("TERMINATED"); + } + else if (300 <= statusCode && statusCode <= 699) { + this.setState("COMPLETED"); + this.enableTimeoutTimer(this.TIMER_H); + } + } + else if (200 <= statusCode && statusCode <= 699) { + this.setState("COMPLETED"); + if (!this.isReliable()) { + this.enableTimeoutTimer(this.TIMER_J); + } + else { + this.setState("TERMINATED"); + } + } + } + else if ("COMPLETED" == this.getRealState()) { + return; + } + try { + this.lastResponse = transactionResponse; + this.sendResponseSRT(transactionResponse); + } catch (ex) { + this.setState("TERMINATED"); + this.collectionTime = 0; + console.error("SIPServerTransaction:sendMessage(): catched exception:"+ex); + } +} + +SIPServerTransaction.prototype.getViaHost =function(){ + return this.getMessageChannel().getViaHost(); +} + +SIPServerTransaction.prototype.getViaPort =function(){ + return this.getMessageChannel().getViaPort(); +} + +SIPServerTransaction.prototype.fireTimeoutTimer =function(){ + clearTimeout(this.timer); + if (this.getMethod()=="INVITE" && this.sipStack.removeTransactionPendingAck(this)) { + return; + } + var dialog = this.dialog; + if (this.getSIPStack().isDialogCreated(this.getOriginalRequest().getMethod()) + && ("CALLING" == this.getRealState() || "TRYING" == this.getRealState())) { + dialog.setState(this.TERMINATED_STATE); + } + else if (this.getOriginalRequest().getMethod()=="BYE") { + if (dialog != null && dialog.isTerminatedOnBye()) { + dialog.setState(this.TERMINATED_STATE); + } + } + if ("COMPLETED" == this.getRealState() && this.isInviteTransaction()) { + this.raiseErrorEvent(this.TIMEOUT_ERROR); + this.setState("TERMINATED"); + this.sipStack.removeTransaction(this); + } + else if ("COMPLETED" == this.getRealState() && !this.isInviteTransaction()) { + this.setState("TERMINATED"); + this.sipStack.removeTransaction(this); + } + else if ("CONFIRMED" == this.getRealState() && this.isInviteTransaction()) { + this.setState("TERMINATED"); + this.sipStack.removeTransaction(this); + } + else if (!isInviteTransaction() + && ("COMPLETED" == this.getRealState() || "CONFIRMED" == this.getRealState())) { + this.setState("TERMINATED"); + } + else if (isInviteTransaction() && "TERMINATED" == this.getRealState()) { + this.raiseErrorEvent(this.TIMEOUT_ERROR); + if (dialog != null) { + dialog.setState(this.TERMINATED_STATE); + } + } +} + +SIPServerTransaction.prototype.getLastResponse =function(){ + return this.lastResponse; +} + +SIPServerTransaction.prototype.sendResponseSRT =function(transactionResponse){ + this.getMessageChannel().sendMessage(transactionResponse); + this.startTransactionTimer(); +} + +SIPServerTransaction.prototype.sendResponse =function(response){ + var sipResponse = response; + var dialog = this.dialog; + if (response == null) { + console.error("SIPServerTransaction:sendResponse(): null response argument"); + throw "SIPServerTransaction:sendResponse(): null response argument"; + } + try { + sipResponse.checkHeaders(); + } catch (ex) { + console.error("SIPServerTransaction:sendMessage(): catched exception:"+ex); + throw "SIPServerTransaction:sendResponse(): catched exception:"+ex; + } + + + if (sipResponse.getCSeq().getMethod()!=this.getMethod()) { + console.error("SIPServerTransaction:sendResponse(): CSeq method does not match Request method of request that created the tx."); + throw "SIPServerTransaction:sendResponse(): CSeq method does not match Request method of request that created the tx."; + } + + if (this.getMethod()==("SUBSCRIBE") && response.getStatusCode() / 100 == 2) { + if (response.getHeader(this.ExpiresHeader) == null) { + console.error("SIPServerTransaction:sendResponse(): Expires header is mandatory in 2xx response of SUBSCRIBE"); + throw "SIPServerTransaction:sendResponse(): Expires header is mandatory in 2xx response of SUBSCRIBE"; + } else { + var requestExpires = this.getOriginalRequest().getExpires(); + var responseExpires = response.getExpires(); + if (requestExpires != null + && responseExpires.getExpires() > requestExpires.getExpires()) { + console.error("SIPServerTransaction:sendResponse(): response Expires time exceeds request Expires time : See RFC 3265 3.1.1"); + throw "SIPServerTransaction:sendResponse():Response Expires time exceeds request Expires time : See RFC 3265 3.1.1"; + } + } + } + + if (sipResponse.getStatusCode() == 200 + && sipResponse.getCSeq().getMethod()=="INVITE" + && sipResponse.getHeader(this.ContactHeader) == null) { + console.error("SIPServerTransaction:sendResponse(): Contact Header is mandatory for the OK to the INVITE"); + throw "SIPServerTransaction:sendResponse(): Contact Header is mandatory for the OK to the INVITE"; + } + + if (!this.isMessagePartOfTransaction(response)) { + console.error("SIPServerTransaction:sendResponse(): response does not belong to this transaction."); + throw "SIPServerTransaction:sendResponse(): response does not belong to this transaction."; + } + + try { + if (dialog != null) { + if (sipResponse.getStatusCode() / 100 == 2 + && this.sipStack.isDialogCreated(sipResponse.getCSeq().getMethod())) { + if (dialog.getLocalTag() == null && sipResponse.getTo().getTag() == null) { + sipResponse.getTo().setTag(Utils.prototype.generateTag()); + } + else if (dialog.getLocalTag() != null && sipResponse.getToTag() == null) { + sipResponse.setToTag(dialog.getLocalTag()); + } + else if (dialog.getLocalTag() != null && sipResponse.getToTag() != null + && dialog.getLocalTag()!=sipResponse.getToTag()) { + console.error("SIPServerTransaction:sendResponse(): tag mismatch dialogTag is " + dialog.getLocalTag() + " responseTag is "+ sipResponse.getToTag()); + throw "SIPServerTransaction:sendResponse(): tag mismatch dialogTag is " + dialog.getLocalTag() + " responseTag is "+ sipResponse.getToTag(); + } + } + if (sipResponse.getCallId().getCallId()!=dialog.getCallId().getCallId()) { + console.error("SIPServerTransaction:sendResponse(): dialog mismatch!"); + throw "SIPServerTransaction:sendResponse(): dialog mismatch!"; + } + } + + var fromTag = this.getRequest().getFrom().getTag(); + if (fromTag != null && sipResponse.getFromTag() != null + && sipResponse.getFromTag()!=fromTag) { + console.error("SIPServerTransaction:sendResponse(): from tag of request does not match response from tag"); + throw "SIPServerTransaction:sendResponse(): from tag of request does not match response from tag"; + } + else if (fromTag != null) { + sipResponse.getFrom().setTag(fromTag); + } + if (dialog != null && response.getStatusCode() != 100) { + dialog.setResponseTags(sipResponse); + var oldState = dialog.getState(); + dialog.setLastResponse(this, response); + if (oldState == null && dialog.getState() == "TERMINATED") { + var event = new DialogTerminatedEvent(dialog.getSipProvider(), dialog); + dialog.getSipProvider().handleEvent(event, this); + } + } + this.sendMessage(response); + } catch (ex) { + this.setState("TERMINATED"); + this.raiseErrorEvent(this.TRANSPORT_ERROR); + console.error("SIPServerTransaction:sendMessage(): catched exception:"+ex); + } +} + +SIPServerTransaction.prototype.getRealState =function(){ + return SIPTransaction.prototype.getState.call(this); +} + +SIPServerTransaction.prototype.getState =function(){ + if (this.isInviteTransaction() && "TRYING" == SIPTransaction.prototype.getState.call(this)) { + return "PROCEEDING"; + } + else { + return SIPTransaction.prototype.getState.call(this); + } +} + +SIPServerTransaction.prototype.setState =function(newState){ + if (newState == "TERMINATED" && this.isReliable()) { + this.collectionTime = this.TIMER_J; + } + SIPTransaction.prototype.setState.call(this,newState); +} + +SIPServerTransaction.prototype.startTransactionTimer =function(){ + if(this.transactionTimerStarted==false) + { + this.transactionTimerStarted=true; + } + if (this.transactionTimerStarted) { + if (this.timer == null) { + var that=this; + this.timer=setInterval(function(){ + if(!that.isTerminated()){ + that.fireTimer(); + } + }, this.BASE_TIMER_INTERVAL); + } + } +} + +SIPServerTransaction.prototype.getDialog =function(){ + return this.dialog; +} + +SIPServerTransaction.prototype.setDialog =function(sipDialog,dialogId){ + this.dialog = sipDialog; + if (dialogId != null) { + this.dialog.setAssigned(); + } +} + +SIPServerTransaction.prototype.terminate =function(){ + this.setState("TERMINATED"); +} + +SIPServerTransaction.prototype.setAckSeen =function(){ + this.isAckSeen = true; +} + +SIPServerTransaction.prototype.ackSeen =function(){ + return this.isAckSeen; +} + +SIPServerTransaction.prototype.setMapped =function(b){ + this.isMapped = true; +} + +SIPServerTransaction.prototype.setPendingSubscribe =function(pendingSubscribeClientTx){ + this.pendingSubscribeTransaction = pendingSubscribeClientTx; +} + +SIPServerTransaction.prototype.setInviteTransaction =function(st){ + this.inviteTransaction = st; +} + +SIPServerTransaction.prototype.getCanceledInviteTransaction =function(){ + return this.inviteTransaction; +} + +SIPServerTransaction.prototype.scheduleAckRemoval =function(){ + if (this.getMethod() == null || this.getMethod()!="ACK") { + console.error("SIPServerTransaction:scheduleAckRemoval(): method is null[" + (this.getMethod() == null)+ "] or method is not ACK[" + this.getMethod() + "]"); + throw "SIPServerTransaction:scheduleAckRemoval(): method is null[" + (this.getMethod() == null)+ "] or method is not ACK[" + this.getMethod() + "]"; + } + this.startTransactionTimer(); +} + +SIPServerTransaction.prototype.map =function(){ + var realState = this.getRealState(); + if (realState == null || realState == "TRYING") { + this.isMapped = true; + } + this.sipStack.removePendingTransaction(this); +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP Utils . + * @see gov/nist/javax/sip/Utils.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function Utils() { + this.classname="Utils"; +} + +Utils.prototype.BRANCH_MAGIC_COOKIE="z9hG4bK"; +Utils.prototype.callIDCounter=0; +Utils.prototype.counter=0; +Utils.prototype.toHex = ["0", "1", "2", "3", "4", "5", "6","7", "8", "9", "a", "b", "c", "d", "e", "f" ]; +Utils.prototype.rand=Math.random(); + +Utils.prototype.toHexString =function(b){ + var c = ""; + for (var i = 0; i < b.length; i++) { + c=c+Utils.prototype.toHex[(b[i] >> 4) & 0x0F]; + c=c+Utils.prototype.toHex[b[i] & 0x0f]; + } + return c; +} + +Utils.prototype.getQuotedString =function(str){ + return '"' + str.replace( "\"", "\\\"" ) + '"'; +} + +Utils.prototype.generateCallIdentifier =function(address){ + var date = new Date().getTime() + Utils.prototype.callIDCounter+Math.round(Utils.prototype.rand*100000000000000000000); + Utils.prototype.callIDCounter++; + var x=new String(Utils.prototype.getBytes(date.toString())) + var cid = Utils.prototype.digest(x); + var cidString = cid; + return cidString + "@" + address; +} + +Utils.prototype.generateTag =function(){ + var x=Math.round(Utils.prototype.rand*10000000000); + return x.toString(16); +} + +Utils.prototype.generateBranchId =function(){ + var date=new Date().getTime(); + var roundValue = Math.round(Utils.prototype.rand*1000000000); + var num = roundValue+Utils.prototype.counter+date; + Utils.prototype.counter++; + var bid= Utils.prototype.digest(new String(Utils.prototype.getBytes(num.toString()))); + return Utils.prototype.BRANCH_MAGIC_COOKIE + "-"+ Utils.prototype.signature +"-"+ bid; +} + +Utils.prototype.responseBelongsToUs =function(response){ + var topmostVia = response.getTopmostVia(); + var branch = topmostVia.getBranch(); + var x=branch.length-1; + var j=0; + for(var i=Utils.prototype.signature.length-1;i>=0;i--) + { + if(Utils.prototype.signature[i]==branch[x]) + { + j=j+1; + x=x-1; + } + } + if(branch != null && j==Utils.prototype.signature.length) + { + return true; + } + else + { + return false; + } +} + + +Utils.prototype.getSignature =function(){ + return Utils.prototype.signature; +} + +Utils.prototype.randomString= function(stringLength) { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var randomString = ''; + for (var i=0; i>>(32-iShiftBits)); + } + + function AddUnsigned(lX,lY) { + var lX4,lY4,lX8,lY8,lResult; + lX8 = (lX & 0x80000000); + lY8 = (lY & 0x80000000); + lX4 = (lX & 0x40000000); + lY4 = (lY & 0x40000000); + lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF); + if (lX4 & lY4) { + return (lResult ^ 0x80000000 ^ lX8 ^ lY8); + } + if (lX4 | lY4) { + if (lResult & 0x40000000) { + return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); + } else { + return (lResult ^ 0x40000000 ^ lX8 ^ lY8); + } + } else { + return (lResult ^ lX8 ^ lY8); + } + } + + function F(x,y,z) { + return (x & y) | ((~x) & z); + } + function G(x,y,z) { + return (x & z) | (y & (~z)); + } + function H(x,y,z) { + return (x ^ y ^ z); + } + function I(x,y,z) { + return (y ^ (x | (~z))); + } + + function FF(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + }; + + function GG(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + }; + + function HH(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + }; + + function II(a,b,c,d,x,s,ac) { + a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac)); + return AddUnsigned(RotateLeft(a, s), b); + }; + + function ConvertToWordArray(string) { + var lWordCount; + var lMessageLength = string.length; + var lNumberOfWords_temp1=lMessageLength + 8; + var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64; + var lNumberOfWords = (lNumberOfWords_temp2+1)*16; + var lWordArray=Array(lNumberOfWords-1); + var lBytePosition = 0; + var lByteCount = 0; + while ( lByteCount < lMessageLength ) { + lWordCount = (lByteCount-(lByteCount % 4))/4; + lBytePosition = (lByteCount % 4)*8; + lWordArray[lWordCount] = (lWordArray[lWordCount] | (string.charCodeAt(lByteCount)<>>29; + return lWordArray; + }; + + function WordToHex(lValue) { + var WordToHexValue="",WordToHexValue_temp="",lByte,lCount; + for (lCount = 0;lCount<=3;lCount++) { + lByte = (lValue>>>(lCount*8)) & 255; + WordToHexValue_temp = "0" + lByte.toString(16); + WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2); + } + return WordToHexValue; + }; + + function Utf8Encode(string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + + return utftext; + }; + + var x=Array(); + var k,AA,BB,CC,DD,a,b,c,d; + var S11=7, S12=12, S13=17, S14=22; + var S21=5, S22=9 , S23=14, S24=20; + var S31=4, S32=11, S33=16, S34=23; + var S41=6, S42=10, S43=15, S44=21; + + string = Utf8Encode(string); + + x = ConvertToWordArray(string); + + a = 0x67452301; + b = 0xEFCDAB89; + c = 0x98BADCFE; + d = 0x10325476; + + for (k=0;k 0) + { + this.refCount--; + } + if (this.refCount == 0) { + this.isStopped = true; + //this.eventMutex.notify(); + } +} + +EventScanner.prototype.forceStop =function(){ + this.isStopped = true; + this.refCount = 0; +//this.eventMutex.notify(); +} + +EventScanner.prototype.deliverEvent =function(eventWrapper){ + var sipEvent = eventWrapper.sipEvent; + var sipListener = this.sipStack.getSipListener(); + if (sipEvent instanceof RequestEvent) { + var sipRequest = sipEvent.getRequest(); + var tx = this.sipStack.findTransaction(sipRequest, true); + if (tx != null && !tx.passToListener()) { + return; + } + else if (this.sipStack.findPendingTransaction(sipRequest) != null) { + return; + } + else { + var st = eventWrapper.transaction; + this.sipStack.putPendingTransaction(st); + } + sipRequest.setTransaction(eventWrapper.transaction); + if (sipListener != null) + { + sipListener.processRequest(sipEvent); + } + if (eventWrapper.transaction != null) { + var dialog = eventWrapper.transaction.getDialog(); + if (dialog != null) + { + dialog.requestConsumed(); + } + } + if (eventWrapper.transaction != null) + { + this.sipStack.removePendingTransaction(eventWrapper.transaction); + } + if (eventWrapper.transaction.getOriginalRequest().getMethod()=="ACK") { + + eventWrapper.transaction.setState("TERMINATED"); + } + } + else if (sipEvent instanceof ResponseEvent) { + var responseEvent = sipEvent; + var sipResponse = responseEvent.getResponse(); + var sipDialog = responseEvent.getDialog(); + if (sipListener != null) { + tx = eventWrapper.transaction; + if (tx != null) { + tx.setPassToListener(); + } + sipListener.processResponse(sipEvent);//for the level application + } + if ((sipDialog != null && (sipDialog.getState() == null || sipDialog.getState()!="TERMINATED")) + && (sipResponse.getStatusCode() == 481 || sipResponse.getStatusCode() == 408)) { + sipDialog.doDeferredDelete(); + } + if (sipResponse.getCSeq().getMethod()=="INVITE"&& sipDialog != null + && sipResponse.getStatusCode() == 200) { + sipDialog.doDeferredDeleteIfNoAckSent(sipResponse.getCSeq().getSeqNumber()); + } + var ct = eventWrapper.transaction; + if (ct != null && "COMPLETED" == ct.getState() && ct.getOriginalRequest() != null + && ct.getOriginalRequest().getMethod()!="INVITE") { + ct.clearState(); + } + } else if (sipEvent instanceof TimeoutEvent) { + if (sipListener != null) + { + sipListener.processTimeout(sipEvent);//the application level will process the infomation + } + } else if (sipEvent instanceof DialogTimeoutEvent) { + if (sipListener != null) { + sipListener.processDialogTimeout(sipEvent); //the application level will process the infomation + } + } else if (sipEvent instanceof TransactionTerminatedEvent) { + if (sipListener != null) + { + sipListener.processTransactionTerminated(sipEvent);//the application level will process the infomation + } + } else if (sipEvent instanceof DialogTerminatedEvent) { + if (sipListener != null) + { + sipListener.processDialogTerminated(sipEvent);//the application level will process the infomation + } + } +} + +EventScanner.prototype.run =function(){ + while (true) { + var eventWrapper = null; + var eventsToDeliver; + while (this.pendingEvents.length==0) { + if (this.isStopped) { + return; + } + try { + //setTimeout(); + //eventMutex.wait(threadHandle.getPingIntervalInMillisecs()); + } catch (ex) { + console.error("EventScanner:run(): catched exception:"+ex); + return; + } + } + eventsToDeliver = this.pendingEvents; + this.pendingEvents = new Array(); + for(var i=0;i= 200||sipResponse.getStatusCode()<=299)) { + if ((sipResponse.getStatusCode()>= 200||sipResponse.getStatusCode()<=299) + && sipResponse.getCSeq().getMethod()=="INVITE") { + var ackRequest = sipDialog.createAck(sipResponse.getCSeq().getSeqNumber()); + sipDialog.sendAck(ackRequest); + } + return; + } + else { + var ackAlreadySent = false; + if (sipDialog.isAckSeen() && sipDialog.getLastAckSent() != null) { + if (sipDialog.getLastAckSent().getCSeq().getSeqNumber() == sipResponse.getCSeq().getSeqNumber() + && sipResponse.getDialogId(false)==sipDialog.getLastAckSent().getDialogId(false)) { + ackAlreadySent = true; + } + } + if (ackAlreadySent && sipResponse.getCSeq().getMethod()==sipDialog.getMethod()) { + sipDialog.resendAck(); + return; + } + } + } + + } + if (sipDialog != null && sipResponse.getStatusCode() != 100 + && sipResponse.getTo().getTag() != null) { + sipDialog.setLastResponse(transaction, sipResponse); + } + var responseEvent = new ResponseEventExt(sipProvider,transaction,sipDialog,sipResponse); + if (sipResponse.getCSeq().getMethod()=="INVITE") { + var originalTx = this.sipStack.getForkedTransaction(sipResponse.getTransactionId()); + responseEvent.setOriginalTransaction(originalTx); + } + sipProvider.handleEvent(responseEvent, transaction); +} + +DialogFilter.prototype.processResponseargu3 =function(response,incomingMessageChannel,dialog){ + if (this.listeningPoint == null) { + return; + } + if (this.sipStack.checkBranchId && !Utils.prototype.responseBelongsToUs(response)) { + return; + } + var sipProvider = this.listeningPoint.getProvider(); + if (sipProvider == null) { + return; + } + if (sipProvider.getSipListener() == null) { + return; + } + var transaction = this.transactionChannel; + if (transaction == null) { + if (dialog != null) { + if (response.getStatusCode()<200||response.getStatusCode()>299) { + return; + } else if (dialog.getState() == "TERMINATED") { + return; + } else { + var ackAlreadySent = false; + if (dialog.isAckSeen() && dialog.getLastAckSent() != null) { + if (dialog.getLastAckSent().getCSeq().getSeqNumber() == response.getCSeq().getSeqNumber()) { + ackAlreadySent = true; + } + } + if (ackAlreadySent + && response.getCSeq().getMethod()==dialog.getMethod()) { + dialog.resendAck(); + return; + } + } + } + var sipEvent = new ResponseEventExt(sipProvider,transaction,dialog,response); + if (response.getCSeqHeader().getMethod()=="INVITE") { + var forked = this.sipStack.getForkedTransaction(response.getTransactionId()); + sipEvent.setOriginalTransaction(forked); + } + sipProvider.handleEvent(sipEvent, transaction); + return; + } + var responseEvent = null; + responseEvent = new ResponseEventExt(sipProvider,transaction,dialog,response); + if (response.getCSeqHeader().getMethod()=="INVITE") { + responseEvent.setOriginalTransaction(transaction); + } + if (dialog != null && response.getStatusCode() != 100) { + dialog.setLastResponse(transaction, response); + transaction.setDialog(dialog, dialog.getDialogId()); + } + sipProvider.handleEvent(responseEvent, transaction); +} + +DialogFilter.prototype.getSipStack =function(){ + return this.sipStack; +} + +DialogFilter.prototype.sendBadRequestResponse =function(sipRequest,transaction,reasonPhrase){ + var sipResponse = sipRequest.createResponse(400); + if (reasonPhrase != null) + { + sipResponse.setReasonPhrase(reasonPhrase); + } + var serverHeader = MessageFactoryImpl.prototype.getDefaultServerHeader(); + if (serverHeader != null) { + sipResponse.setHeader(serverHeader); + } + if (sipRequest.getMethod()=="INVITE") { + this.sipStack.addTransactionPendingAck(transaction); + } + transaction.sendResponse(sipResponse); +} + + +DialogFilter.prototype.sendCallOrTransactionDoesNotExistResponse =function(sipRequest,transaction){ + var sipResponse = sipRequest.createResponse(481); + var serverHeader = MessageFactoryImpl.prototype.getDefaultServerHeader(); + if (serverHeader != null) { + sipResponse.setHeader(serverHeader); + } + if (sipRequest.getMethod()=="INVITE") { + this.sipStack.addTransactionPendingAck(transaction); + } + transaction.sendResponse(sipResponse); +} + +DialogFilter.prototype.sendLoopDetectedResponse =function(sipRequest,transaction){ + var sipResponse = sipRequest.createResponse(482); + var serverHeader = MessageFactoryImpl.prototype.getDefaultServerHeader(); + if (serverHeader != null) { + sipResponse.setHeader(serverHeader); + } + this.sipStack.addTransactionPendingAck(transaction); + transaction.sendResponse(sipResponse); +} + +DialogFilter.prototype.processRequest =function(sipRequest,incomingMessageChannel){ + if (this.listeningPoint == null) { + return; + } + var sipStack = this.transactionChannel.getSIPStack(); + var sipProvider = this.listeningPoint.getProvider(); + if (sipProvider == null) { + return; + } + var transaction = this.transactionChannel; + var dialogId = sipRequest.getDialogId(true); + var dialog = sipStack.getDialog(dialogId); + if (dialog != null && sipProvider != dialog.getSipProvider()) { + var contact = dialog.getMyContactHeader(); + if (contact != null) { + var contactUri = contact.getAddress().getURI(); + var ipAddress = contactUri.getHost(); + var contactPort = contactUri.getPort(); + if (contactPort == -1) { + contactPort = 5060; + } + if (ipAddress != null + && (ipAddress!=this.listeningPoint.getHostAddress() || contactPort != this.listeningPoint.getPort())) { + dialog = null; + } + } + } + if (sipProvider.isAutomaticDialogSupportEnabled() + && sipProvider.isDialogErrorsAutomaticallyHandled() + && sipRequest.getToTag() == null) { + var sipServerTransaction = sipStack.findMergedTransaction(sipRequest); + if (sipServerTransaction != null) { + this.sendLoopDetectedResponse(sipRequest, transaction); + return; + } + } + if (sipRequest.getMethod()=="ACK") { + if (dialog == null) { + var ackTransaction = sipStack.findTransactionPendingAck(sipRequest); + if (ackTransaction != null) { + ackTransaction.setAckSeen(); + sipStack.removeTransaction(ackTransaction); + sipStack.removeTransactionPendingAck(ackTransaction); + return; + } + } + else { + if (!dialog.handleAck(transaction)) { + ackTransaction = sipStack.findTransactionPendingAck(sipRequest); + if (ackTransaction != null) { + ackTransaction.setAckSeen(); + sipStack.removeTransaction(ackTransaction); + sipStack.removeTransactionPendingAck(ackTransaction); + } + return; + } + else { + transaction.passToListener(); + dialog.addTransaction(transaction); + dialog.addRoute(sipRequest); + transaction.setDialog(dialog, dialogId); + if (sipRequest.getMethod()=="INVITE" + && sipProvider.isDialogErrorsAutomaticallyHandled()) { + sipStack.putInMergeTable(transaction, sipRequest); + } + if (sipStack.deliverTerminatedEventForAck) { + sipStack.addTransaction(transaction); + transaction.scheduleAckRemoval(); + } else { + transaction.setMapped(true); + } + } + } + } + else if (sipRequest.getMethod()=="BYE") { + if (dialog != null && !dialog.isRequestConsumable(sipRequest)) { + if (dialog.getRemoteSeqNumber() >= sipRequest.getCSeq().getSeqNumber() + && transaction.getState() == "TRYING") { + this.sendServerInternalErrorResponse(sipRequest, transaction); + } + if (transaction != null) + { + sipStack.removeTransaction(transaction); + } + return; + } + else if (dialog == null && sipProvider.isAutomaticDialogSupportEnabled()) { + var response = sipRequest.createResponse(481); + response.setReasonPhrase("Dialog Not Found"); + transaction.sendResponse(response); + if (transaction != null) { + sipStack.removeTransaction(transaction); + transaction = null; + } + return; + } + if (transaction != null && dialog != null) { + if (sipProvider == dialog.getSipProvider()) { + sipStack.addTransaction(transaction); + dialog.addTransaction(transaction); + transaction.setDialog(dialog, dialogId); + } + } + } + else if (sipRequest.getMethod()=="CANCEL") { + var st = sipStack.findCancelTransaction(sipRequest, true); + if (sipRequest.getMethod()=="CANCEL") { + if (st != null && st.getState() == "TERMINATED") { + transaction.sendResponse(sipRequest.createResponse("OK")); + return; + } + } + if (transaction != null && st != null && st.getDialog() != null) { + transaction.setDialog(st.getDialog(), dialogId); + dialog = st.getDialog(); + } + else if (st == null && sipProvider.isAutomaticDialogSupportEnabled() + && transaction != null) { + response = sipRequest.createResponse(481); + sipProvider.sendResponse(response); + if (transaction != null) { + sipStack.removeTransaction(transaction); + } + return; + } + if (st != null) { + if (transaction != null) { + sipStack.addTransaction(transaction); + transaction.setPassToListener(); + transaction.setInviteTransaction(st); + } + } + } + else if (sipRequest.getMethod()=="INVITE") { + var lastTransaction = dialog == null ? null : dialog.getInviteTransaction(); + /* + * RFC 3261 Chapter 14. A UAS that receives a second INVITE before + * it sends the final response to a first INVITE with a lower CSeq + * sequence number on the same dialog MUST return a 500 (Server + * Internal Error) response to the second INVITE and MUST include a + * Retry-After header field with a randomly chosen value of between + * 0 and 10 seconds. + */ + if (dialog != null && transaction != null && lastTransaction != null + && sipRequest.getCSeq().getSeqNumber() > lastTransaction.getCSeq() + && lastTransaction instanceof SIPServerTransaction + && sipProvider.isDialogErrorsAutomaticallyHandled() + && dialog.isSequnceNumberValidation() + && lastTransaction.isInviteTransaction() + && lastTransaction.getState() != "COMPLETED" + && lastTransaction.getState() != "TERMINATED" + && lastTransaction.getState() != "CONFIRMED") { + this.sendServerInternalErrorResponse(sipRequest, transaction); + return; + } + + lastTransaction = (dialog == null ? null : dialog.getLastTransaction()); + if (dialog != null + && sipProvider.isDialogErrorsAutomaticallyHandled() + && lastTransaction != null + && lastTransaction.isInviteTransaction() + && lastTransaction instanceof SIPClientTransaction + && lastTransaction.getLastResponse() != null + && lastTransaction.getLastResponse().getStatusCode() == 200 + && !dialog.isAckSent(lastTransaction.getLastResponse().getCSeq() + .getSeqNumber())) { + this.sendRequestPendingResponse(sipRequest, transaction); + return; + } + if (dialog != null && lastTransaction != null + && sipProvider.isDialogErrorsAutomaticallyHandled() + && lastTransaction.isInviteTransaction() + && lastTransaction instanceof ServerTransaction && !dialog.isAckSeen()) { + this.sendRequestPendingResponse(sipRequest, transaction); + return; + } + } + if (dialog != null && transaction != null && sipRequest.getMethod()!="BYE" + && sipRequest.getMethod()!="CANCEL" + && sipRequest.getMethod()!="ACK") { + if (!dialog.isRequestConsumable(sipRequest)) { + if (dialog.getRemoteSeqNumber() >= sipRequest.getCSeq().getSeqNumber() + && sipProvider.isDialogErrorsAutomaticallyHandled() + && (transaction.getState() == "TRYING" || transaction.getState() == "PROCEEDING")) { + this.sendServerInternalErrorResponse(sipRequest, transaction); + } + return; + } + try { + if (sipProvider == dialog.getSipProvider()) { + sipStack.addTransaction(transaction); + dialog.addTransaction(transaction); + dialog.addRoute(sipRequest); + transaction.setDialog(dialog, dialogId); + } + } catch (ex) { + console.error("DialogFilter:processRequest(): catched exception:"+ex); + sipStack.removeTransaction(transaction); + return; + } + + } + var sipEvent; + if (transaction != null) { + sipEvent = new RequestEvent(sipProvider, transaction, dialog, sipRequest); + } + else { + sipEvent = new RequestEvent(sipProvider, null, dialog, sipRequest); + } + sipProvider.handleEvent(sipEvent, transaction); +} + +DialogFilter.prototype.getProcessingInfo =function(){ + return null; +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP ListeningPointImpl . + * @see gov/nist/javax/sip/ListeningPointImpl.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function ListeningPointImpl(sipStack, messageProcessor) { + this.classname="ListeningPointImpl"; + this.sipStack=sipStack; + this.messageProcessor=messageProcessor; + this.sipProvider=null; +} + +ListeningPointImpl.prototype.makeKey =function(host,transport){ + return (""+host+"/"+transport).toLowerCase(); +} + + +ListeningPointImpl.prototype.getKey =function(){ + return this.makeKey(this.sipStack.getHostAddress(), this.messageProcessor.getTransport()); +} + +ListeningPointImpl.prototype.setSipProvider =function(sipProviderImpl){ + this.sipProvider = sipProviderImpl; +} + +ListeningPointImpl.prototype.removeSipProvider =function(){ + this.sipProvider = null; +} + +ListeningPointImpl.prototype.getTransport =function(){ + return this.messageProcessor.getTransport(); +} + +ListeningPointImpl.prototype.getProvider =function(){ + return this.sipProvider; +} + +ListeningPointImpl.prototype.setSentBy =function(sentBy){ + this.messageProcessor.setSentBy(sentBy); +} + +ListeningPointImpl.prototype.getSentBy =function(){ + return this.messageProcessor.getSentBy(); +} + +ListeningPointImpl.prototype.isSentBySet =function(){ + return this.messageProcessor.isSentBySet(); +} + +ListeningPointImpl.prototype.getViaHeader =function(){ + return this.messageProcessor.getViaHeader(); +} + +ListeningPointImpl.prototype.getMessageProcessor =function(){ + return this.messageProcessor; +} + +ListeningPointImpl.prototype.createContactHeader =function(userName){ + try { + var hostname = this.sipStack.getHostAddress(); + var sipURI = new SipUri(); + sipURI.setHost_String(hostname); + sipURI.setUser(userName); + sipURI.setTransportParam(this.messageProcessor.getTransport()); + var contact = new Contact(); + var address = new AddressImpl(); + address.setURI(sipURI); + contact.setAddress(address); + return contact; + } catch (ex) { + console.error("ListeningPointImpl:createContactHeader(): catched exception:"+ex); + return null; + } +} + +ListeningPointImpl.prototype.sendHeartbeat =function(infoApp){ + var messageChannel = this.messageProcessor.createMessageChannel(infoApp); + var siprequest = new SIPRequest(); + siprequest.setNullRequest(); + messageChannel.sendMessage(siprequest); +} + +ListeningPointImpl.prototype.getPort =function(){ + return this.messageProcessor.getPort(); +} + +ListeningPointImpl.prototype.getHostAddress =function(){ + return this.sipStack.getHostAddress(); +} +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP NistSipMessageFactoryImpl . + * @see gov/nist/javax/sip/NistSipMessageFactoryImpl.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function NistSipMessageFactoryImpl(sipStack) { + this.classname="NistSipMessageFactoryImpl"; + this.sipStack=sipStack; +} + +NistSipMessageFactoryImpl.prototype.newSIPServerRequest =function(sipRequest,messageChannel){ + if (messageChannel == null || sipRequest == null) { + console.error("NistSipMessageFactoryImpl:newSIPServerRequest(): null Arg!"); + throw "NistSipMessageFactoryImpl:newSIPServerRequest(): null Arg!"; + } + var theStack = messageChannel.getSIPStack(); + var retval = new DialogFilter(theStack); + if (messageChannel instanceof SIPTransaction) { + retval.transactionChannel = messageChannel; + } + retval.listeningPoint = messageChannel.getMessageProcessor().getListeningPoint(); + if (retval.listeningPoint == null) + { + return null; + } + return retval; +} + +NistSipMessageFactoryImpl.prototype.newSIPServerResponse =function(sipResponse,messageChannel){ + var theStack = messageChannel.getSIPStack(); + var tr = theStack.findTransaction(sipResponse, false); + if (tr != null) { + if (tr.getState() == null) { + return null; + } + else if ("COMPLETED" == tr.getState() + && sipResponse.getStatusCode() / 100 == 1) { + return null; + } + } + var retval = new DialogFilter(this.sipStack); + retval.transactionChannel = tr; + retval.listeningPoint = messageChannel.getMessageProcessor().getListeningPoint(); + return retval; +}/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +/* + * Implementation of the JAIN-SIP SipProviderImpl . + * @see gov/nist/javax/sip/SipProviderImpl.java + * @author Yuemin Qin (yuemin.qin@orange.com) + * @author Laurent STRULLU (laurent.strullu@orange.com) + * @version 1.0 + */ +function SipProviderImpl(sipStack) { + this.classname="SipProviderImpl"; + this.sipListener=null; + this.sipStack=null; + this.listeningPoints=new Array(); + this.eventScanner=null; + this.address=null; + this.port=null; + this.automaticDialogSupportEnabled=null; + this.dialogErrorsAutomaticallyHandled = true; + this.sipStack=sipStack; + this.eventScanner = sipStack.getEventScanner(); // for quick access. + this.eventScanner.incrementRefcount(); + this.listeningPoints = new Array(); + this.automaticDialogSupportEnabled = this.sipStack.isAutomaticDialogSupportEnabledFunction(); + this.dialogErrorsAutomaticallyHandled = this.sipStack.isAutomaticDialogErrorHandlingEnabledFunction(); +} + +SipProviderImpl.prototype.getListeningPoint =function(transport){ + for(var i=0; i=0&&c0&&b-1 in a)}var x=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C={}.hasOwnProperty,D=[],E=D.pop,F=D.push,G=D.push,H=D.slice,I=function(a,b){for(var c=0,d=a.length;c+~]|"+K+")"+K+"*"),S=new RegExp("="+K+"*([^\\]'\"]*?)"+K+"*\\]","g"),T=new RegExp(N),U=new RegExp("^"+L+"$"),V={ID:new RegExp("^#("+L+")"),CLASS:new RegExp("^\\.("+L+")"),TAG:new RegExp("^("+L+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+K+"*(even|odd|(([+-]|)(\\d*)n|)"+K+"*(?:([+-]|)"+K+"*(\\d+)|))"+K+"*\\)|)","i"),bool:new RegExp("^(?:"+J+")$","i"),needsContext:new RegExp("^"+K+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+K+"*((?:-\\d)?\\d*)"+K+"*\\)|)(?=[^-]|$)","i")},W=/^(?:input|select|textarea|button)$/i,X=/^h\d$/i,Y=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,$=/[+~]/,_=new RegExp("\\\\([\\da-f]{1,6}"+K+"?|("+K+")|.)","ig"),aa=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:d<0?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ba=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ca=function(a,b){return b?"\0"===a?"\ufffd":a.slice(0,-1)+"\\"+a.charCodeAt(a.length-1).toString(16)+" ":"\\"+a},da=function(){m()},ea=ta(function(a){return a.disabled===!0&&("form"in a||"label"in a)},{dir:"parentNode",next:"legend"});try{G.apply(D=H.call(v.childNodes),v.childNodes),D[v.childNodes.length].nodeType}catch(fa){G={apply:D.length?function(a,b){F.apply(a,H.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s=b&&b.ownerDocument,w=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==w&&9!==w&&11!==w)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==w&&(l=Z.exec(a)))if(f=l[1]){if(9===w){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(s&&(j=s.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(l[2])return G.apply(d,b.getElementsByTagName(a)),d;if((f=l[3])&&c.getElementsByClassName&&b.getElementsByClassName)return G.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==w)s=b,r=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(ba,ca):b.setAttribute("id",k=u),o=g(a),h=o.length;while(h--)o[h]="#"+k+" "+sa(o[h]);r=o.join(","),s=$.test(a)&&qa(b.parentNode)||b}if(r)try{return G.apply(d,s.querySelectorAll(r)),d}catch(x){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(P,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("fieldset");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&a.sourceIndex-b.sourceIndex;if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return function(b){return"form"in b?b.parentNode&&b.disabled===!1?"label"in b?"label"in b.parentNode?b.parentNode.disabled===a:b.disabled===a:b.isDisabled===a||b.isDisabled!==!a&&ea(b)===a:b.disabled===a:"label"in b&&b.disabled===a}}function pa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function qa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return!!b&&"HTML"!==b.nodeName},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),v!==n&&(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Y.test(n.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){return a.getAttribute("id")===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}}):(d.filter.ID=function(a){var b=a.replace(_,aa);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}},d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c,d,e,f=b.getElementById(a);if(f){if(c=f.getAttributeNode("id"),c&&c.value===a)return[f];e=b.getElementsByName(a),d=0;while(f=e[d++])if(c=f.getAttributeNode("id"),c&&c.value===a)return[f]}return[]}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){if("undefined"!=typeof b.getElementsByClassName&&p)return b.getElementsByClassName(a)},r=[],q=[],(c.qsa=Y.test(n.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+K+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+K+"*(?:value|"+J+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){a.innerHTML="";var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+K+"*[*^$|!~]?="),2!==a.querySelectorAll(":enabled").length&&q.push(":enabled",":disabled"),o.appendChild(a).disabled=!0,2!==a.querySelectorAll(":disabled").length&&q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Y.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"*"),s.call(a,"[s!='']:x"),r.push("!=",N)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Y.test(o.compareDocumentPosition),t=b||Y.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?I(k,a)-I(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?I(k,a)-I(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?la(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(S,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&C.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.escape=function(a){return(a+"").replace(ba,ca)},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(_,aa),a[3]=(a[3]||a[4]||a[5]||"").replace(_,aa),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return V.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&T.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(_,aa).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+K+")"+a+"("+K+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:!b||(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(O," ")+" ").indexOf(c)>-1:"|="===b&&(e===c||e.slice(0,c.length+1)===c+"-"))}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=I(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(P,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(_,aa),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return U.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(_,aa).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:oa(!1),disabled:oa(!0),checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return X.test(a.nodeName)},input:function(a){return W.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:pa(function(){return[0]}),last:pa(function(a,b){return[b-1]}),eq:pa(function(a,b,c){return[c<0?c+b:c]}),even:pa(function(a,b){for(var c=0;c=0;)a.push(d);return a}),gt:pa(function(a,b,c){for(var d=c<0?c+b:c;++d1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function va(a,b,c){for(var d=0,e=b.length;d-1&&(f[j]=!(g[j]=l))}}else r=wa(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):G.apply(g,r)})}function ya(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ta(function(a){return a===b},h,!0),l=ta(function(a){return I(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];i1&&ua(m),i>1&&sa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(P,"$1"),c,i0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=E.call(i));u=wa(u)}G.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&ga.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=ya(b[c]),f[u]?d.push(f):e.push(f);f=A(a,za(e,d)),f.selector=a}return f},i=ga.select=function(a,b,c,e){var f,i,j,k,l,m="function"==typeof a&&a,n=!e&&g(a=m.selector||a);if(c=c||[],1===n.length){if(i=n[0]=n[0].slice(0),i.length>2&&"ID"===(j=i[0]).type&&9===b.nodeType&&p&&d.relative[i[1].type]){if(b=(d.find.ID(j.matches[0].replace(_,aa),b)||[])[0],!b)return c;m&&(b=b.parentNode),a=a.slice(i.shift().value.length)}f=V.needsContext.test(a)?0:i.length;while(f--){if(j=i[f],d.relative[k=j.type])break;if((l=d.find[k])&&(e=l(j.matches[0].replace(_,aa),$.test(i[0].type)&&qa(b.parentNode)||b))){if(i.splice(f,1),a=e.length&&sa(i),!a)return G.apply(c,e),c;break}}}return(m||h(a,n))(e,b,!p,c,!b||$.test(a)&&qa(b.parentNode)||b),c},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("fieldset"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){if(!c)return a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){if(!c&&"input"===a.nodeName.toLowerCase())return a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(J,function(a,b,c){var d;if(!c)return a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);r.find=x,r.expr=x.selectors,r.expr[":"]=r.expr.pseudos,r.uniqueSort=r.unique=x.uniqueSort,r.text=x.getText,r.isXMLDoc=x.isXML,r.contains=x.contains,r.escapeSelector=x.escape;var y=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&r(a).is(c))break;d.push(a)}return d},z=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},A=r.expr.match.needsContext,B=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i,C=/^.[^:#\[\.,]*$/;function D(a,b,c){return r.isFunction(b)?r.grep(a,function(a,d){return!!b.call(a,d,a)!==c}):b.nodeType?r.grep(a,function(a){return a===b!==c}):"string"!=typeof b?r.grep(a,function(a){return i.call(b,a)>-1!==c}):C.test(b)?r.filter(b,a,c):(b=r.filter(b,a),r.grep(a,function(a){return i.call(b,a)>-1!==c&&1===a.nodeType}))}r.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?r.find.matchesSelector(d,a)?[d]:[]:r.find.matches(a,r.grep(b,function(a){return 1===a.nodeType}))},r.fn.extend({find:function(a){var b,c,d=this.length,e=this;if("string"!=typeof a)return this.pushStack(r(a).filter(function(){for(b=0;b1?r.uniqueSort(c):c},filter:function(a){return this.pushStack(D(this,a||[],!1))},not:function(a){return this.pushStack(D(this,a||[],!0))},is:function(a){return!!D(this,"string"==typeof a&&A.test(a)?r(a):a||[],!1).length}});var E,F=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,G=r.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||E,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:F.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof r?b[0]:b,r.merge(this,r.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),B.test(e[1])&&r.isPlainObject(b))for(e in b)r.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&(this[0]=f,this.length=1),this}return a.nodeType?(this[0]=a,this.length=1,this):r.isFunction(a)?void 0!==c.ready?c.ready(a):a(r):r.makeArray(a,this)};G.prototype=r.fn,E=r(d);var H=/^(?:parents|prev(?:Until|All))/,I={children:!0,contents:!0,next:!0,prev:!0};r.fn.extend({has:function(a){var b=r(a,this),c=b.length;return this.filter(function(){for(var a=0;a-1:1===c.nodeType&&r.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?r.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?i.call(r(a),this[0]):i.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(r.uniqueSort(r.merge(this.get(),r(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function J(a,b){while((a=a[b])&&1!==a.nodeType);return a}r.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return y(a,"parentNode")},parentsUntil:function(a,b,c){return y(a,"parentNode",c)},next:function(a){return J(a,"nextSibling")},prev:function(a){return J(a,"previousSibling")},nextAll:function(a){return y(a,"nextSibling")},prevAll:function(a){return y(a,"previousSibling")},nextUntil:function(a,b,c){return y(a,"nextSibling",c)},prevUntil:function(a,b,c){return y(a,"previousSibling",c)},siblings:function(a){return z((a.parentNode||{}).firstChild,a)},children:function(a){return z(a.firstChild)},contents:function(a){return a.contentDocument||r.merge([],a.childNodes)}},function(a,b){r.fn[a]=function(c,d){var e=r.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=r.filter(d,e)),this.length>1&&(I[a]||r.uniqueSort(e),H.test(a)&&e.reverse()),this.pushStack(e)}});var K=/[^\x20\t\r\n\f]+/g;function L(a){var b={};return r.each(a.match(K)||[],function(a,c){b[c]=!0}),b}r.Callbacks=function(a){a="string"==typeof a?L(a):r.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),c<=h&&h--}),this},has:function(a){return a?r.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||b||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j};function M(a){return a}function N(a){throw a}function O(a,b,c){var d;try{a&&r.isFunction(d=a.promise)?d.call(a).done(b).fail(c):a&&r.isFunction(d=a.then)?d.call(a,b,c):b.call(void 0,a)}catch(a){c.call(void 0,a)}}r.extend({Deferred:function(b){var c=[["notify","progress",r.Callbacks("memory"),r.Callbacks("memory"),2],["resolve","done",r.Callbacks("once memory"),r.Callbacks("once memory"),0,"resolved"],["reject","fail",r.Callbacks("once memory"),r.Callbacks("once memory"),1,"rejected"]],d="pending",e={state:function(){return d},always:function(){return f.done(arguments).fail(arguments),this},"catch":function(a){return e.then(null,a)},pipe:function(){var a=arguments;return r.Deferred(function(b){r.each(c,function(c,d){var e=r.isFunction(a[d[4]])&&a[d[4]];f[d[1]](function(){var a=e&&e.apply(this,arguments);a&&r.isFunction(a.promise)?a.promise().progress(b.notify).done(b.resolve).fail(b.reject):b[d[0]+"With"](this,e?[a]:arguments)})}),a=null}).promise()},then:function(b,d,e){var f=0;function g(b,c,d,e){return function(){var h=this,i=arguments,j=function(){var a,j;if(!(b=f&&(d!==N&&(h=void 0,i=[a]),c.rejectWith(h,i))}};b?k():(r.Deferred.getStackHook&&(k.stackTrace=r.Deferred.getStackHook()),a.setTimeout(k))}}return r.Deferred(function(a){c[0][3].add(g(0,a,r.isFunction(e)?e:M,a.notifyWith)),c[1][3].add(g(0,a,r.isFunction(b)?b:M)),c[2][3].add(g(0,a,r.isFunction(d)?d:N))}).promise()},promise:function(a){return null!=a?r.extend(a,e):e}},f={};return r.each(c,function(a,b){var g=b[2],h=b[5];e[b[1]]=g.add,h&&g.add(function(){d=h},c[3-a][2].disable,c[0][2].lock),g.add(b[3].fire),f[b[0]]=function(){return f[b[0]+"With"](this===f?void 0:this,arguments),this},f[b[0]+"With"]=g.fireWith}),e.promise(f),b&&b.call(f,f),f},when:function(a){var b=arguments.length,c=b,d=Array(c),e=f.call(arguments),g=r.Deferred(),h=function(a){return function(c){d[a]=this,e[a]=arguments.length>1?f.call(arguments):c,--b||g.resolveWith(d,e)}};if(b<=1&&(O(a,g.done(h(c)).resolve,g.reject),"pending"===g.state()||r.isFunction(e[c]&&e[c].then)))return g.then();while(c--)O(e[c],h(c),g.reject);return g.promise()}});var P=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;r.Deferred.exceptionHook=function(b,c){a.console&&a.console.warn&&b&&P.test(b.name)&&a.console.warn("jQuery.Deferred exception: "+b.message,b.stack,c)},r.readyException=function(b){a.setTimeout(function(){throw b})};var Q=r.Deferred();r.fn.ready=function(a){return Q.then(a)["catch"](function(a){r.readyException(a)}),this},r.extend({isReady:!1,readyWait:1,holdReady:function(a){a?r.readyWait++:r.ready(!0)},ready:function(a){(a===!0?--r.readyWait:r.isReady)||(r.isReady=!0,a!==!0&&--r.readyWait>0||Q.resolveWith(d,[r]))}}),r.ready.then=Q.then;function R(){d.removeEventListener("DOMContentLoaded",R), +a.removeEventListener("load",R),r.ready()}"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(r.ready):(d.addEventListener("DOMContentLoaded",R),a.addEventListener("load",R));var S=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===r.type(c)){e=!0;for(h in c)S(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,r.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(r(a),c)})),b))for(;h1,null,!0)},removeData:function(a){return this.each(function(){W.remove(this,a)})}}),r.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=V.get(a,b),c&&(!d||r.isArray(c)?d=V.access(a,b,r.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=r.queue(a,b),d=c.length,e=c.shift(),f=r._queueHooks(a,b),g=function(){r.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return V.get(a,c)||V.access(a,c,{empty:r.Callbacks("once memory").add(function(){V.remove(a,[b+"queue",c])})})}}),r.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length\x20\t\r\n\f]+)/i,ka=/^$|\/(?:java|ecma)script/i,la={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};la.optgroup=la.option,la.tbody=la.tfoot=la.colgroup=la.caption=la.thead,la.th=la.td;function ma(a,b){var c;return c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[],void 0===b||b&&r.nodeName(a,b)?r.merge([a],c):c}function na(a,b){for(var c=0,d=a.length;c-1)e&&e.push(f);else if(j=r.contains(f.ownerDocument,f),g=ma(l.appendChild(f),"script"),j&&na(g),c){k=0;while(f=g[k++])ka.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),o.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="",o.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var qa=d.documentElement,ra=/^key/,sa=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,ta=/^([^.]*)(?:\.(.+)|)/;function ua(){return!0}function va(){return!1}function wa(){try{return d.activeElement}catch(a){}}function xa(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)xa(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=va;else if(!e)return a;return 1===f&&(g=e,e=function(a){return r().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=r.guid++)),a.each(function(){r.event.add(this,b,e,d,c)})}r.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.get(a);if(q){c.handler&&(f=c,c=f.handler,e=f.selector),e&&r.find.matchesSelector(qa,e),c.guid||(c.guid=r.guid++),(i=q.events)||(i=q.events={}),(g=q.handle)||(g=q.handle=function(b){return"undefined"!=typeof r&&r.event.triggered!==b.type?r.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(K)||[""],j=b.length;while(j--)h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n&&(l=r.event.special[n]||{},n=(e?l.delegateType:l.bindType)||n,l=r.event.special[n]||{},k=r.extend({type:n,origType:p,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&r.expr.match.needsContext.test(e),namespace:o.join(".")},f),(m=i[n])||(m=i[n]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,o,g)!==!1||a.addEventListener&&a.addEventListener(n,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),r.event.global[n]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,p,q=V.hasData(a)&&V.get(a);if(q&&(i=q.events)){b=(b||"").match(K)||[""],j=b.length;while(j--)if(h=ta.exec(b[j])||[],n=p=h[1],o=(h[2]||"").split(".").sort(),n){l=r.event.special[n]||{},n=(d?l.delegateType:l.bindType)||n,m=i[n]||[],h=h[2]&&new RegExp("(^|\\.)"+o.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&p!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,o,q.handle)!==!1||r.removeEvent(a,n,q.handle),delete i[n])}else for(n in i)r.event.remove(a,n+b[j],c,d,!0);r.isEmptyObject(i)&&V.remove(a,"handle events")}},dispatch:function(a){var b=r.event.fix(a),c,d,e,f,g,h,i=new Array(arguments.length),j=(V.get(this,"events")||{})[b.type]||[],k=r.event.special[b.type]||{};for(i[0]=b,c=1;c=1))for(;j!==this;j=j.parentNode||this)if(1===j.nodeType&&("click"!==a.type||j.disabled!==!0)){for(f=[],g={},c=0;c-1:r.find(e,this,null,[j]).length),g[e]&&f.push(d);f.length&&h.push({elem:j,handlers:f})}return j=this,i\x20\t\r\n\f]*)[^>]*)\/>/gi,za=/\s*$/g;function Da(a,b){return r.nodeName(a,"table")&&r.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a:a}function Ea(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function Fa(a){var b=Ba.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function Ga(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(V.hasData(a)&&(f=V.access(a),g=V.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;c1&&"string"==typeof q&&!o.checkClone&&Aa.test(q))return a.each(function(e){var f=a.eq(e);s&&(b[0]=q.call(this,e,f.html())),Ia(f,b,c,d)});if(m&&(e=pa(b,a[0].ownerDocument,!1,a,d),f=e.firstChild,1===e.childNodes.length&&(e=f),f||d)){for(h=r.map(ma(e,"script"),Ea),i=h.length;l")},clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=r.contains(a.ownerDocument,a);if(!(o.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||r.isXMLDoc(a)))for(g=ma(h),f=ma(a),d=0,e=f.length;d0&&na(g,!i&&ma(a,"script")),h},cleanData:function(a){for(var b,c,d,e=r.event.special,f=0;void 0!==(c=a[f]);f++)if(T(c)){if(b=c[V.expando]){if(b.events)for(d in b.events)e[d]?r.event.remove(c,d):r.removeEvent(c,d,b.handle);c[V.expando]=void 0}c[W.expando]&&(c[W.expando]=void 0)}}}),r.fn.extend({detach:function(a){return Ja(this,a,!0)},remove:function(a){return Ja(this,a)},text:function(a){return S(this,function(a){return void 0===a?r.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=a)})},null,a,arguments.length)},append:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.appendChild(a)}})},prepend:function(){return Ia(this,arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=Da(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return Ia(this,arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(r.cleanData(ma(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null!=a&&a,b=null==b?a:b,this.map(function(){return r.clone(this,a,b)})},html:function(a){return S(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!za.test(a)&&!la[(ja.exec(a)||["",""])[1].toLowerCase()]){a=r.htmlPrefilter(a);try{for(;c1)}});function Ya(a,b,c,d,e){return new Ya.prototype.init(a,b,c,d,e)}r.Tween=Ya,Ya.prototype={constructor:Ya,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||r.easing._default,this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(r.cssNumber[c]?"":"px")},cur:function(){var a=Ya.propHooks[this.prop];return a&&a.get?a.get(this):Ya.propHooks._default.get(this)},run:function(a){var b,c=Ya.propHooks[this.prop];return this.options.duration?this.pos=b=r.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):Ya.propHooks._default.set(this),this}},Ya.prototype.init.prototype=Ya.prototype,Ya.propHooks={_default:{get:function(a){var b;return 1!==a.elem.nodeType||null!=a.elem[a.prop]&&null==a.elem.style[a.prop]?a.elem[a.prop]:(b=r.css(a.elem,a.prop,""),b&&"auto"!==b?b:0)},set:function(a){r.fx.step[a.prop]?r.fx.step[a.prop](a):1!==a.elem.nodeType||null==a.elem.style[r.cssProps[a.prop]]&&!r.cssHooks[a.prop]?a.elem[a.prop]=a.now:r.style(a.elem,a.prop,a.now+a.unit)}}},Ya.propHooks.scrollTop=Ya.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},r.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2},_default:"swing"},r.fx=Ya.prototype.init,r.fx.step={};var Za,$a,_a=/^(?:toggle|show|hide)$/,ab=/queueHooks$/;function bb(){$a&&(a.requestAnimationFrame(bb),r.fx.tick())}function cb(){return a.setTimeout(function(){Za=void 0}),Za=r.now()}function db(a,b){var c,d=0,e={height:a};for(b=b?1:0;d<4;d+=2-b)c=ba[d],e["margin"+c]=e["padding"+c]=a;return b&&(e.opacity=e.width=a),e}function eb(a,b,c){for(var d,e=(hb.tweeners[b]||[]).concat(hb.tweeners["*"]),f=0,g=e.length;f1)},removeAttr:function(a){return this.each(function(){r.removeAttr(this,a)})}}),r.extend({attr:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return"undefined"==typeof a.getAttribute?r.prop(a,b,c):(1===f&&r.isXMLDoc(a)||(e=r.attrHooks[b.toLowerCase()]||(r.expr.match.bool.test(b)?ib:void 0)), +void 0!==c?null===c?void r.removeAttr(a,b):e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:(a.setAttribute(b,c+""),c):e&&"get"in e&&null!==(d=e.get(a,b))?d:(d=r.find.attr(a,b),null==d?void 0:d))},attrHooks:{type:{set:function(a,b){if(!o.radioValue&&"radio"===b&&r.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}}},removeAttr:function(a,b){var c,d=0,e=b&&b.match(K);if(e&&1===a.nodeType)while(c=e[d++])a.removeAttribute(c)}}),ib={set:function(a,b,c){return b===!1?r.removeAttr(a,c):a.setAttribute(c,c),c}},r.each(r.expr.match.bool.source.match(/\w+/g),function(a,b){var c=jb[b]||r.find.attr;jb[b]=function(a,b,d){var e,f,g=b.toLowerCase();return d||(f=jb[g],jb[g]=e,e=null!=c(a,b,d)?g:null,jb[g]=f),e}});var kb=/^(?:input|select|textarea|button)$/i,lb=/^(?:a|area)$/i;r.fn.extend({prop:function(a,b){return S(this,r.prop,a,b,arguments.length>1)},removeProp:function(a){return this.each(function(){delete this[r.propFix[a]||a]})}}),r.extend({prop:function(a,b,c){var d,e,f=a.nodeType;if(3!==f&&8!==f&&2!==f)return 1===f&&r.isXMLDoc(a)||(b=r.propFix[b]||b,e=r.propHooks[b]),void 0!==c?e&&"set"in e&&void 0!==(d=e.set(a,c,b))?d:a[b]=c:e&&"get"in e&&null!==(d=e.get(a,b))?d:a[b]},propHooks:{tabIndex:{get:function(a){var b=r.find.attr(a,"tabindex");return b?parseInt(b,10):kb.test(a.nodeName)||lb.test(a.nodeName)&&a.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),o.optSelected||(r.propHooks.selected={get:function(a){var b=a.parentNode;return b&&b.parentNode&&b.parentNode.selectedIndex,null},set:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex)}}),r.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){r.propFix[this.toLowerCase()]=this});function mb(a){var b=a.match(K)||[];return b.join(" ")}function nb(a){return a.getAttribute&&a.getAttribute("class")||""}r.fn.extend({addClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).addClass(a.call(this,b,nb(this)))});if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])d.indexOf(" "+f+" ")<0&&(d+=f+" ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},removeClass:function(a){var b,c,d,e,f,g,h,i=0;if(r.isFunction(a))return this.each(function(b){r(this).removeClass(a.call(this,b,nb(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof a&&a){b=a.match(K)||[];while(c=this[i++])if(e=nb(c),d=1===c.nodeType&&" "+mb(e)+" "){g=0;while(f=b[g++])while(d.indexOf(" "+f+" ")>-1)d=d.replace(" "+f+" "," ");h=mb(d),e!==h&&c.setAttribute("class",h)}}return this},toggleClass:function(a,b){var c=typeof a;return"boolean"==typeof b&&"string"===c?b?this.addClass(a):this.removeClass(a):r.isFunction(a)?this.each(function(c){r(this).toggleClass(a.call(this,c,nb(this),b),b)}):this.each(function(){var b,d,e,f;if("string"===c){d=0,e=r(this),f=a.match(K)||[];while(b=f[d++])e.hasClass(b)?e.removeClass(b):e.addClass(b)}else void 0!==a&&"boolean"!==c||(b=nb(this),b&&V.set(this,"__className__",b),this.setAttribute&&this.setAttribute("class",b||a===!1?"":V.get(this,"__className__")||""))})},hasClass:function(a){var b,c,d=0;b=" "+a+" ";while(c=this[d++])if(1===c.nodeType&&(" "+mb(nb(c))+" ").indexOf(b)>-1)return!0;return!1}});var ob=/\r/g;r.fn.extend({val:function(a){var b,c,d,e=this[0];{if(arguments.length)return d=r.isFunction(a),this.each(function(c){var e;1===this.nodeType&&(e=d?a.call(this,c,r(this).val()):a,null==e?e="":"number"==typeof e?e+="":r.isArray(e)&&(e=r.map(e,function(a){return null==a?"":a+""})),b=r.valHooks[this.type]||r.valHooks[this.nodeName.toLowerCase()],b&&"set"in b&&void 0!==b.set(this,e,"value")||(this.value=e))});if(e)return b=r.valHooks[e.type]||r.valHooks[e.nodeName.toLowerCase()],b&&"get"in b&&void 0!==(c=b.get(e,"value"))?c:(c=e.value,"string"==typeof c?c.replace(ob,""):null==c?"":c)}}}),r.extend({valHooks:{option:{get:function(a){var b=r.find.attr(a,"value");return null!=b?b:mb(r.text(a))}},select:{get:function(a){var b,c,d,e=a.options,f=a.selectedIndex,g="select-one"===a.type,h=g?null:[],i=g?f+1:e.length;for(d=f<0?i:g?f:0;d-1)&&(c=!0);return c||(a.selectedIndex=-1),f}}}}),r.each(["radio","checkbox"],function(){r.valHooks[this]={set:function(a,b){if(r.isArray(b))return a.checked=r.inArray(r(a).val(),b)>-1}},o.checkOn||(r.valHooks[this].get=function(a){return null===a.getAttribute("value")?"on":a.value})});var pb=/^(?:focusinfocus|focusoutblur)$/;r.extend(r.event,{trigger:function(b,c,e,f){var g,h,i,j,k,m,n,o=[e||d],p=l.call(b,"type")?b.type:b,q=l.call(b,"namespace")?b.namespace.split("."):[];if(h=i=e=e||d,3!==e.nodeType&&8!==e.nodeType&&!pb.test(p+r.event.triggered)&&(p.indexOf(".")>-1&&(q=p.split("."),p=q.shift(),q.sort()),k=p.indexOf(":")<0&&"on"+p,b=b[r.expando]?b:new r.Event(p,"object"==typeof b&&b),b.isTrigger=f?2:3,b.namespace=q.join("."),b.rnamespace=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=e),c=null==c?[b]:r.makeArray(c,[b]),n=r.event.special[p]||{},f||!n.trigger||n.trigger.apply(e,c)!==!1)){if(!f&&!n.noBubble&&!r.isWindow(e)){for(j=n.delegateType||p,pb.test(j+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),i=h;i===(e.ownerDocument||d)&&o.push(i.defaultView||i.parentWindow||a)}g=0;while((h=o[g++])&&!b.isPropagationStopped())b.type=g>1?j:n.bindType||p,m=(V.get(h,"events")||{})[b.type]&&V.get(h,"handle"),m&&m.apply(h,c),m=k&&h[k],m&&m.apply&&T(h)&&(b.result=m.apply(h,c),b.result===!1&&b.preventDefault());return b.type=p,f||b.isDefaultPrevented()||n._default&&n._default.apply(o.pop(),c)!==!1||!T(e)||k&&r.isFunction(e[p])&&!r.isWindow(e)&&(i=e[k],i&&(e[k]=null),r.event.triggered=p,e[p](),r.event.triggered=void 0,i&&(e[k]=i)),b.result}},simulate:function(a,b,c){var d=r.extend(new r.Event,c,{type:a,isSimulated:!0});r.event.trigger(d,null,b)}}),r.fn.extend({trigger:function(a,b){return this.each(function(){r.event.trigger(a,b,this)})},triggerHandler:function(a,b){var c=this[0];if(c)return r.event.trigger(a,b,c,!0)}}),r.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(a,b){r.fn[b]=function(a,c){return arguments.length>0?this.on(b,null,a,c):this.trigger(b)}}),r.fn.extend({hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),o.focusin="onfocusin"in a,o.focusin||r.each({focus:"focusin",blur:"focusout"},function(a,b){var c=function(a){r.event.simulate(b,a.target,r.event.fix(a))};r.event.special[b]={setup:function(){var d=this.ownerDocument||this,e=V.access(d,b);e||d.addEventListener(a,c,!0),V.access(d,b,(e||0)+1)},teardown:function(){var d=this.ownerDocument||this,e=V.access(d,b)-1;e?V.access(d,b,e):(d.removeEventListener(a,c,!0),V.remove(d,b))}}});var qb=a.location,rb=r.now(),sb=/\?/;r.parseXML=function(b){var c;if(!b||"string"!=typeof b)return null;try{c=(new a.DOMParser).parseFromString(b,"text/xml")}catch(d){c=void 0}return c&&!c.getElementsByTagName("parsererror").length||r.error("Invalid XML: "+b),c};var tb=/\[\]$/,ub=/\r?\n/g,vb=/^(?:submit|button|image|reset|file)$/i,wb=/^(?:input|select|textarea|keygen)/i;function xb(a,b,c,d){var e;if(r.isArray(b))r.each(b,function(b,e){c||tb.test(a)?d(a,e):xb(a+"["+("object"==typeof e&&null!=e?b:"")+"]",e,c,d)});else if(c||"object"!==r.type(b))d(a,b);else for(e in b)xb(a+"["+e+"]",b[e],c,d)}r.param=function(a,b){var c,d=[],e=function(a,b){var c=r.isFunction(b)?b():b;d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(null==c?"":c)};if(r.isArray(a)||a.jquery&&!r.isPlainObject(a))r.each(a,function(){e(this.name,this.value)});else for(c in a)xb(c,a[c],b,e);return d.join("&")},r.fn.extend({serialize:function(){return r.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var a=r.prop(this,"elements");return a?r.makeArray(a):this}).filter(function(){var a=this.type;return this.name&&!r(this).is(":disabled")&&wb.test(this.nodeName)&&!vb.test(a)&&(this.checked||!ia.test(a))}).map(function(a,b){var c=r(this).val();return null==c?null:r.isArray(c)?r.map(c,function(a){return{name:b.name,value:a.replace(ub,"\r\n")}}):{name:b.name,value:c.replace(ub,"\r\n")}}).get()}});var yb=/%20/g,zb=/#.*$/,Ab=/([?&])_=[^&]*/,Bb=/^(.*?):[ \t]*([^\r\n]*)$/gm,Cb=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Db=/^(?:GET|HEAD)$/,Eb=/^\/\//,Fb={},Gb={},Hb="*/".concat("*"),Ib=d.createElement("a");Ib.href=qb.href;function Jb(a){return function(b,c){"string"!=typeof b&&(c=b,b="*");var d,e=0,f=b.toLowerCase().match(K)||[];if(r.isFunction(c))while(d=f[e++])"+"===d[0]?(d=d.slice(1)||"*",(a[d]=a[d]||[]).unshift(c)):(a[d]=a[d]||[]).push(c)}}function Kb(a,b,c,d){var e={},f=a===Gb;function g(h){var i;return e[h]=!0,r.each(a[h]||[],function(a,h){var j=h(b,c,d);return"string"!=typeof j||f||e[j]?f?!(i=j):void 0:(b.dataTypes.unshift(j),g(j),!1)}),i}return g(b.dataTypes[0])||!e["*"]&&g("*")}function Lb(a,b){var c,d,e=r.ajaxSettings.flatOptions||{};for(c in b)void 0!==b[c]&&((e[c]?a:d||(d={}))[c]=b[c]);return d&&r.extend(!0,a,d),a}function Mb(a,b,c){var d,e,f,g,h=a.contents,i=a.dataTypes;while("*"===i[0])i.shift(),void 0===d&&(d=a.mimeType||b.getResponseHeader("Content-Type"));if(d)for(e in h)if(h[e]&&h[e].test(d)){i.unshift(e);break}if(i[0]in c)f=i[0];else{for(e in c){if(!i[0]||a.converters[e+" "+i[0]]){f=e;break}g||(g=e)}f=f||g}if(f)return f!==i[0]&&i.unshift(f),c[f]}function Nb(a,b,c,d){var e,f,g,h,i,j={},k=a.dataTypes.slice();if(k[1])for(g in a.converters)j[g.toLowerCase()]=a.converters[g];f=k.shift();while(f)if(a.responseFields[f]&&(c[a.responseFields[f]]=b),!i&&d&&a.dataFilter&&(b=a.dataFilter(b,a.dataType)),i=f,f=k.shift())if("*"===f)f=i;else if("*"!==i&&i!==f){if(g=j[i+" "+f]||j["* "+f],!g)for(e in j)if(h=e.split(" "),h[1]===f&&(g=j[i+" "+h[0]]||j["* "+h[0]])){g===!0?g=j[e]:j[e]!==!0&&(f=h[0],k.unshift(h[1]));break}if(g!==!0)if(g&&a["throws"])b=g(b);else try{b=g(b)}catch(l){return{state:"parsererror",error:g?l:"No conversion from "+i+" to "+f}}}return{state:"success",data:b}}r.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:qb.href,type:"GET",isLocal:Cb.test(qb.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Hb,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":r.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(a,b){return b?Lb(Lb(a,r.ajaxSettings),b):Lb(r.ajaxSettings,a)},ajaxPrefilter:Jb(Fb),ajaxTransport:Jb(Gb),ajax:function(b,c){"object"==typeof b&&(c=b,b=void 0),c=c||{};var e,f,g,h,i,j,k,l,m,n,o=r.ajaxSetup({},c),p=o.context||o,q=o.context&&(p.nodeType||p.jquery)?r(p):r.event,s=r.Deferred(),t=r.Callbacks("once memory"),u=o.statusCode||{},v={},w={},x="canceled",y={readyState:0,getResponseHeader:function(a){var b;if(k){if(!h){h={};while(b=Bb.exec(g))h[b[1].toLowerCase()]=b[2]}b=h[a.toLowerCase()]}return null==b?null:b},getAllResponseHeaders:function(){return k?g:null},setRequestHeader:function(a,b){return null==k&&(a=w[a.toLowerCase()]=w[a.toLowerCase()]||a,v[a]=b),this},overrideMimeType:function(a){return null==k&&(o.mimeType=a),this},statusCode:function(a){var b;if(a)if(k)y.always(a[y.status]);else for(b in a)u[b]=[u[b],a[b]];return this},abort:function(a){var b=a||x;return e&&e.abort(b),A(0,b),this}};if(s.promise(y),o.url=((b||o.url||qb.href)+"").replace(Eb,qb.protocol+"//"),o.type=c.method||c.type||o.method||o.type,o.dataTypes=(o.dataType||"*").toLowerCase().match(K)||[""],null==o.crossDomain){j=d.createElement("a");try{j.href=o.url,j.href=j.href,o.crossDomain=Ib.protocol+"//"+Ib.host!=j.protocol+"//"+j.host}catch(z){o.crossDomain=!0}}if(o.data&&o.processData&&"string"!=typeof o.data&&(o.data=r.param(o.data,o.traditional)),Kb(Fb,o,c,y),k)return y;l=r.event&&o.global,l&&0===r.active++&&r.event.trigger("ajaxStart"),o.type=o.type.toUpperCase(),o.hasContent=!Db.test(o.type),f=o.url.replace(zb,""),o.hasContent?o.data&&o.processData&&0===(o.contentType||"").indexOf("application/x-www-form-urlencoded")&&(o.data=o.data.replace(yb,"+")):(n=o.url.slice(f.length),o.data&&(f+=(sb.test(f)?"&":"?")+o.data,delete o.data),o.cache===!1&&(f=f.replace(Ab,"$1"),n=(sb.test(f)?"&":"?")+"_="+rb++ +n),o.url=f+n),o.ifModified&&(r.lastModified[f]&&y.setRequestHeader("If-Modified-Since",r.lastModified[f]),r.etag[f]&&y.setRequestHeader("If-None-Match",r.etag[f])),(o.data&&o.hasContent&&o.contentType!==!1||c.contentType)&&y.setRequestHeader("Content-Type",o.contentType),y.setRequestHeader("Accept",o.dataTypes[0]&&o.accepts[o.dataTypes[0]]?o.accepts[o.dataTypes[0]]+("*"!==o.dataTypes[0]?", "+Hb+"; q=0.01":""):o.accepts["*"]);for(m in o.headers)y.setRequestHeader(m,o.headers[m]);if(o.beforeSend&&(o.beforeSend.call(p,y,o)===!1||k))return y.abort();if(x="abort",t.add(o.complete),y.done(o.success),y.fail(o.error),e=Kb(Gb,o,c,y)){if(y.readyState=1,l&&q.trigger("ajaxSend",[y,o]),k)return y;o.async&&o.timeout>0&&(i=a.setTimeout(function(){y.abort("timeout")},o.timeout));try{k=!1,e.send(v,A)}catch(z){if(k)throw z;A(-1,z)}}else A(-1,"No Transport");function A(b,c,d,h){var j,m,n,v,w,x=c;k||(k=!0,i&&a.clearTimeout(i),e=void 0,g=h||"",y.readyState=b>0?4:0,j=b>=200&&b<300||304===b,d&&(v=Mb(o,y,d)),v=Nb(o,v,y,j),j?(o.ifModified&&(w=y.getResponseHeader("Last-Modified"),w&&(r.lastModified[f]=w),w=y.getResponseHeader("etag"),w&&(r.etag[f]=w)),204===b||"HEAD"===o.type?x="nocontent":304===b?x="notmodified":(x=v.state,m=v.data,n=v.error,j=!n)):(n=x,!b&&x||(x="error",b<0&&(b=0))),y.status=b,y.statusText=(c||x)+"",j?s.resolveWith(p,[m,x,y]):s.rejectWith(p,[y,x,n]),y.statusCode(u),u=void 0,l&&q.trigger(j?"ajaxSuccess":"ajaxError",[y,o,j?m:n]),t.fireWith(p,[y,x]),l&&(q.trigger("ajaxComplete",[y,o]),--r.active||r.event.trigger("ajaxStop")))}return y},getJSON:function(a,b,c){return r.get(a,b,c,"json")},getScript:function(a,b){return r.get(a,void 0,b,"script")}}),r.each(["get","post"],function(a,b){r[b]=function(a,c,d,e){return r.isFunction(c)&&(e=e||d,d=c,c=void 0),r.ajax(r.extend({url:a,type:b,dataType:e,data:c,success:d},r.isPlainObject(a)&&a))}}),r._evalUrl=function(a){return r.ajax({url:a,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},r.fn.extend({wrapAll:function(a){var b;return this[0]&&(r.isFunction(a)&&(a=a.call(this[0])),b=r(a,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstElementChild)a=a.firstElementChild;return a}).append(this)),this},wrapInner:function(a){return r.isFunction(a)?this.each(function(b){r(this).wrapInner(a.call(this,b))}):this.each(function(){var b=r(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=r.isFunction(a);return this.each(function(c){r(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(a){return this.parent(a).not("body").each(function(){r(this).replaceWith(this.childNodes)}),this}}),r.expr.pseudos.hidden=function(a){return!r.expr.pseudos.visible(a)},r.expr.pseudos.visible=function(a){return!!(a.offsetWidth||a.offsetHeight||a.getClientRects().length)},r.ajaxSettings.xhr=function(){try{return new a.XMLHttpRequest}catch(b){}};var Ob={0:200,1223:204},Pb=r.ajaxSettings.xhr();o.cors=!!Pb&&"withCredentials"in Pb,o.ajax=Pb=!!Pb,r.ajaxTransport(function(b){var c,d;if(o.cors||Pb&&!b.crossDomain)return{send:function(e,f){var g,h=b.xhr();if(h.open(b.type,b.url,b.async,b.username,b.password),b.xhrFields)for(g in b.xhrFields)h[g]=b.xhrFields[g];b.mimeType&&h.overrideMimeType&&h.overrideMimeType(b.mimeType),b.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest");for(g in e)h.setRequestHeader(g,e[g]);c=function(a){return function(){c&&(c=d=h.onload=h.onerror=h.onabort=h.onreadystatechange=null,"abort"===a?h.abort():"error"===a?"number"!=typeof h.status?f(0,"error"):f(h.status,h.statusText):f(Ob[h.status]||h.status,h.statusText,"text"!==(h.responseType||"text")||"string"!=typeof h.responseText?{binary:h.response}:{text:h.responseText},h.getAllResponseHeaders()))}},h.onload=c(),d=h.onerror=c("error"),void 0!==h.onabort?h.onabort=d:h.onreadystatechange=function(){4===h.readyState&&a.setTimeout(function(){c&&d()})},c=c("abort");try{h.send(b.hasContent&&b.data||null)}catch(i){if(c)throw i}},abort:function(){c&&c()}}}),r.ajaxPrefilter(function(a){a.crossDomain&&(a.contents.script=!1)}),r.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(a){return r.globalEval(a),a}}}),r.ajaxPrefilter("script",function(a){void 0===a.cache&&(a.cache=!1),a.crossDomain&&(a.type="GET")}),r.ajaxTransport("script",function(a){if(a.crossDomain){var b,c;return{send:function(e,f){b=r("