├── .gitignore ├── LICENSE.md ├── README.md ├── bit-array.js ├── btc.js ├── btc ├── blockchain.js ├── inventory.js ├── mempool.js ├── miner.js └── transactions.js ├── d3.v3.min.js ├── goog ├── array │ └── array.js ├── asserts │ └── asserts.js ├── base.js ├── bootstrap │ ├── nodejs.js │ └── webworkers.js ├── debug │ └── error.js ├── deps.js ├── dom │ └── nodetype.js ├── object │ └── object.js ├── string │ └── string.js └── structs │ ├── heap.js │ ├── node.js │ └── priorityqueue.js ├── hub.js ├── index.html ├── jquery.min.js ├── network.js ├── peermgr.js ├── require.js ├── sim-example-alert.js ├── sim-selfish.js └── sim.js /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ebfull/simbit/ac861de1405c59b4d6e20aa743f5438eaab3db12/.gitignore -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Sean Bowe 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | simbit (alpha) 2 | ====== 3 | 4 | Javascript P2P Network Simulator 5 | -------------------------------- 6 | 7 | simbit is a javascript simulation framework with an emphasis on consensus networks like Bitcoin. It is easy to rapidly prototype 8 | new structures, protocols and concepts, and to understand their effects on latency-sensitive systems. **It is designed for 9 | both in-browser realtime simulation/visualization and clustered simulations using Node.** 10 | 11 | ![Example visualization](http://i.imgur.com/0oSfSw4.gif) 12 | 13 | Running 14 | ------- 15 | 16 | simbit can be invoked with `node sim.js` or by visiting the `index.html` page supplied in this repository. 17 | 18 | Developing with Simbit 19 | ---------------------- 20 | 21 | A boring simulation with 100 nodes looks something like this: 22 | 23 | ```javascript 24 | var net = require("./network") 25 | var client = new net.Client() // create a new node template 26 | 27 | net.add(100, client); // instantiate 100 nodes using the client template 28 | net.run(100 * 1000); // runs for 100 seconds 29 | ``` 30 | 31 | You can set the contents of `sim.js` to the above code and visit `index.html` to see it in action. In this simple example, 32 | none of the nodes will do anything or connect to each other. 33 | 34 | simbit uses a middleware architecture for including modules, and some modules are provided. `peermgr` provides basic networking 35 | mechanisms, latency simulation, peer discovery, buffering and more. 36 | 37 | ```javascript 38 | var net = require("./network"), 39 | peermgr = require("./peermgr") // include peermgr 40 | var client = new net.Client() 41 | 42 | client.use(peermgr) // use the peermgr middleware 43 | 44 | net.add(100, client) 45 | net.run(100 * 1000) 46 | ``` 47 | 48 | Now look at `index.html` -- you will notice the nodes discover and connect to each other. The first node (node 0) is used 49 | as a bootstrap node by peermgr. 50 | 51 | Let's have the client (randomly) select the maximum number of nodes it would like to connect to, when it's initialized. 52 | 53 | ```javascript 54 | var net = require("./network"), 55 | peermgr = require("./peermgr") 56 | var client = new net.Client() 57 | client.use(peermgr) 58 | 59 | client.init(function() { 60 | // this function is called when a node is initialized 61 | this.peermgr.maxpeers = Math.floor(Math.random() * 20) + 8; 62 | }) 63 | 64 | net.add(100, client) 65 | net.run(100 * 1000) 66 | ``` 67 | 68 | ### Events 69 | 70 | Tick events are events that occur to clients at some interval of time. `peermgr` will already use ticks to space out 71 | connection and discovery attempts until it reaches maxpeers. We can use ticks ourselves like this: 72 | 73 | ```javascript 74 | client.init(function() { 75 | this.tick(1000, function() { 76 | // will tick every second 77 | 78 | return false; // if we return false, this tick stops 79 | }) 80 | }) 81 | ``` 82 | 83 | Delayed events are 'one shot' events that occur after a specified amount of time: 84 | ```javascript 85 | client.init(function() { 86 | this.delay(3000, function() { 87 | // this will fire in 3 seconds 88 | }) 89 | }) 90 | ``` 91 | 92 | Probabilistic events are like delayed events, in that they are one shot events, except that the time until the event is 93 | fired is exponentially distributed for discrete event simulation. This is useful for simulating bitcoin mining, for 94 | example. 95 | 96 | ```javascript 97 | client.init(function() { 98 | this.prob("heartbeat", (1/1000), function() { 99 | // We can expect this function to fire in 1000msec on average. 100 | 101 | this.log('wheeeeeeeeee') 102 | }) 103 | }) 104 | ``` 105 | 106 | ### Communication 107 | 108 | Here's a simple ping/pong protocol which uses `peermgr` and `.on` handlers. 109 | 110 | ```javascript 111 | client.init(function() { 112 | this.tick(1000, function () { 113 | // every second we'll broadcast a ping message to our peers, containing a timestamp 114 | this.peermgr.broadcast("ping", this.now()); 115 | }) 116 | 117 | this.on("ping", function(from, time) { 118 | // we received a ping message (courtesy of peermgr) 119 | this.peermgr.send(from, "pong", time); // send back a pong 120 | }) 121 | 122 | this.on("pong", function(from, time) { 123 | // we received a pong message from another peer 124 | this.log("roundtrip: " + (this.now() - time)) 125 | }) 126 | }) 127 | ``` 128 | 129 | Also notice that this uses `this.now()` to get the current simulation time in msec, and `this.log()` for debugging or 130 | statistics. 131 | 132 | ### Middleware 133 | 134 | In addition to `peermgr`, a module `btc` is being created to simulate the Bitcoin reference client. 135 | 136 | You can create your own middleware like so: 137 | 138 | ```javascript 139 | module.exports = function (self) { 140 | self.tick(1000, function() { 141 | // i am a thread that loves wasting cpu! 142 | Math.random() + Math.random() + Math.random() 143 | }) 144 | } 145 | ``` 146 | 147 | Place the above into, say, waster.js, and require() it in sim.js as well: 148 | 149 | ```javascript 150 | var net = require("./network"), 151 | peermgr = require("./peermgr"), 152 | waster = require("./waster") // include our new middleware 153 | 154 | var client = new net.Client() 155 | client.use(peermgr) 156 | client.use(waster) // use the middleware 157 | 158 | net.add(100, client) 159 | net.run(100 * 1000) 160 | ``` 161 | 162 | ### Client-side Delay 163 | 164 | If the client needs to simulate a time-consuming computation, it can use `.delay()` to create an event which occurs once, 165 | in the future. 166 | 167 | ```javascript 168 | client.init(function() { 169 | this.on("tx", function(from, tx) { 170 | // pretend it takes 25msec to verify the transaction 171 | this.delay(25, function() { 172 | // this function will be called in 25msec 173 | }); 174 | }) 175 | }) 176 | ``` 177 | 178 | API 179 | --- 180 | 181 | #### Network 182 | 183 | `var net = require("./network")` 184 | 185 | | Property | Description | 186 | | -------- | ----------- | 187 | | `.Client` | A `Client` class used by `.add()` | 188 | | `.add (n, client)` | Creates `n` nodes, using `client` as a template. `node` is an instance of .Client | 189 | | `.check (t, f)` | `f()` is called every `t` msec of simulation | 190 | | `.run (msec [, next])` | Run `msec` worth of simulation time. next is an optional callback after run completes. | 191 | | `.stop()` | Stops the simulation, existing `.run` tasks will end. | 192 | | `.log(str)` | Logs `str` to console or to the visualizer | 193 | 194 | #### Client 195 | 196 | `var client = new net.Client()` 197 | 198 | | Property | Description | 199 | | -------- | ----------- | 200 | | `.init(f)` | `f()` is called during the node's initialization. | 201 | | `.use(middleware)` | `middleware` is constructed with the `NodeState` as an argument. | 202 | 203 | #### NodeState 204 | 205 | Functions like `.on()` or `.tick()` send the NodeState as function context to the handler. This can be overwritten by the (optional) `thisArg` argument. 206 | 207 | | Property | Description | 208 | | -------- | ----------- | 209 | | `.on(name, f [, thisArg])` | Attaches a handler `f(from, msg)` for event `name`. If `f` returns false, the previously attached handler(s) by this `name` are bypassed. | 210 | | `.tick(t, f [, thisArg])` | Attaches a handler `f` for a tick event that occurs every `t` msec. If `f` returns false, the handler is unattached. If it returns an integer, the tick interval is changed to that integer. | 211 | | `.delay(t, f [, thisArg])` | `f` is called once, in `t` msec. | 212 | | `.prob(name, p, f [, thisArg])` | Attaches a handler `f` for a probabilistic event named `name`, which uses `p` as a rate parameter. | 213 | | `.deprob(name)` | Removes a probabilistic handler named `name`. | 214 | | `.now()` | Returns the current time, in msec, from the start of the simulation | 215 | | `.setColor(str)` | Sets the color of the current node to `str` | 216 | | `.log(str)` | Logs `str` for the node. | 217 | | `.setColor(str)` | Sets the color of the node to `str` within the visualizer | 218 | 219 | #### peermgr 220 | 221 | ```javascript 222 | var net = require("./network"), 223 | peermgr = require("./peermgr"), // include peermgr 224 | client = new net.Client() 225 | 226 | client.use(peermgr) // use it 227 | ``` 228 | 229 | The peermgr middleware is stored in NodeState's `peermgr` property when it is used by the client. 230 | 231 | | Property | Description | 232 | | -------- | ----------- | 233 | | `this.peermgr.maxpeers` | Integer describing the maximum number of peers peermgr should attempt to connect to; best modified at initialization | 234 | | `this.peermgr.send(to, name, msg)` | Sends a message `msg` called `name` to peer number `to` | 235 | | `this.peermgr.each(cb)` | Iterates over all active peers by calling `cb(peerid)` with each peer id | 236 | | `this.peermgr.broadcast(name, msg)` | Broadcasts a message `msg` named `name` to all active peers | 237 | 238 | To handle messages remote nodes send you, use .on() like so: 239 | 240 | ```javascript 241 | client.init(function() { 242 | this.on("alert", function(from, obj) { 243 | // received an alert from our peer! 244 | // contents of message is in obj 245 | }) 246 | }) 247 | ``` 248 | 249 | #### btc 250 | 251 | The `btc` middleware is being developed to simulate the bitcoin reference client. 252 | 253 | ```javascript 254 | var net = require("./network"), 255 | peermgr = require("./peermgr"), 256 | btc = require("./btc"), 257 | client = new net.Client() 258 | 259 | client.use(peermgr) 260 | client.use(btc) 261 | 262 | client.init(function() { 263 | if (this.id == 0) { 264 | var tx1 = this.transactions.create([], 1); // creates a transaction with no inputs (like a coinbase) and one output 265 | var tx2 = this.transactions.create([tx1.in(0)], 1) // creates a transaction which spends the previous transaction 266 | 267 | // Let's enter the transactions into our local UTXO: 268 | this.transactions.enter(tx2) // this will appear in mapOrphans until we enter tx1 269 | this.transactions.enter(tx1) // both tx1 and tx2 will be part of the UTXO now 270 | 271 | // Now let's create inventory objects for these transactions: 272 | this.inventory.createObj('tx', tx1) 273 | this.inventory.createObj('tx', tx2) 274 | 275 | this.delay(30000, function() { 276 | // 30 seconds later, we could start relaying both transactions 277 | this.inventory.relay(tx1.id) 278 | this.inventory.relay(tx2.id) 279 | }) 280 | } 281 | }) 282 | 283 | net.add(100, client); 284 | net.run(200 * 1000) 285 | ``` 286 | 287 | A blockchain system already functions, performing reorgs, storing orphan blocks, and directing blocks to be relayed. 288 | 289 | A miner system is being developed: 290 | 291 | ```javascript 292 | var net = require("./network"), 293 | peermgr = require("./peermgr"), 294 | btc = require("./btc"), 295 | client = new net.Client() 296 | 297 | client.use(peermgr) 298 | client.use(btc) 299 | 300 | var left = 1; // mining resources left 301 | 302 | client.init(function() { 303 | var myResources = Math.random() * left / 3; 304 | left -= myResources; 305 | 306 | this.mine(myResources) 307 | 308 | this.on("miner:success", function(from, b) { 309 | this.log("yay we mined a block (height=" + b.h + ")") 310 | }) 311 | }); 312 | 313 | net.add(200, client) 314 | net.run(Infinity) 315 | ``` 316 | -------------------------------------------------------------------------------- /bit-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JavaScript BitArray - v0.2.0 3 | * 4 | * Licensed under the revised BSD License. 5 | * Copyright 2010-2012 Bram Stein 6 | * All rights reserved. 7 | */ 8 | 9 | /** 10 | * Creates a new empty BitArray with the given length or initialises the BitArray with the given hex representation. 11 | */ 12 | var BitArray = function (size, hex) { 13 | this.values = []; 14 | 15 | if (hex) { 16 | hex = hex.slice(/^0x/.exec(hex) ? 2 : 0); 17 | 18 | if (hex.length * 4 > this.length) { 19 | throw 'Hex value is too large for this bit array.' 20 | } else if (hex.length * 4 < this.length) { 21 | // pad it 22 | while(hex.length * 4 < this.length) { 23 | hex = '0' + hex; 24 | } 25 | } 26 | 27 | for (var i = 0; i < (hex.length / 8); i++) { 28 | var slice = hex.slice(i * 8, i * 8 + 8); 29 | this.values[i] = parseInt(slice, 16); 30 | } 31 | } else { 32 | for (var i = 0; i < Math.ceil(size / 32); i += 1) { 33 | this.values[i] = 0; 34 | } 35 | } 36 | }; 37 | 38 | /** 39 | * Returns the total number of bits in this BitArray. 40 | */ 41 | BitArray.prototype.size = function () { 42 | return this.values.length * 32; 43 | }; 44 | 45 | /** 46 | * Sets the bit at index to a value (boolean.) 47 | */ 48 | BitArray.prototype.set = function (index, value) { 49 | var i = Math.floor(index / 32); 50 | // Since "undefined | 1 << index" is equivalent to "0 | 1 << index" we do not need to initialise the array explicitly here. 51 | if (value) { 52 | this.values[i] |= 1 << index - i * 32; 53 | } else { 54 | this.values[i] &= ~(1 << index - i * 32); 55 | } 56 | return this; 57 | }; 58 | 59 | /** 60 | * Toggles the bit at index. If the bit is on, it is turned off. Likewise, if the bit is off it is turned on. 61 | */ 62 | BitArray.prototype.toggle = function (index) { 63 | var i = Math.floor(index / 32); 64 | this.values[i] ^= 1 << index - i * 32; 65 | return this; 66 | }; 67 | 68 | /** 69 | * Returns the value of the bit at index (boolean.) 70 | */ 71 | BitArray.prototype.get = function (index) { 72 | var i = Math.floor(index / 32); 73 | return !!(this.values[i] & (1 << index - i * 32)); 74 | }; 75 | 76 | /** 77 | * Resets the BitArray so that it is empty and can be re-used. 78 | */ 79 | BitArray.prototype.reset = function () { 80 | this.values = []; 81 | return this; 82 | }; 83 | 84 | /** 85 | * Returns a copy of this BitArray. 86 | */ 87 | BitArray.prototype.copy = function () { 88 | var cp = new BitArray(); 89 | cp.length = this.length; 90 | cp.values = [].concat(this.values); 91 | return cp; 92 | }; 93 | 94 | /** 95 | * Returns true if this BitArray equals another. Two BitArrays are considered 96 | * equal if both have the same length and bit pattern. 97 | */ 98 | BitArray.prototype.equals = function (x) { 99 | return this.values.length === x.values.length && 100 | this.values.every(function (value, index) { 101 | return value === x.values[index]; 102 | }); 103 | }; 104 | 105 | /** 106 | * Returns the JSON representation of this BitArray. 107 | */ 108 | BitArray.prototype.toJSON = function () { 109 | return JSON.stringify(this.values); 110 | }; 111 | 112 | /** 113 | * Returns a string representation of the BitArray with bits 114 | * in logical order. 115 | */ 116 | BitArray.prototype.toString = function () { 117 | return this.toArray().map(function (value) { 118 | return value ? '1' : '0'; 119 | }).join(''); 120 | }; 121 | 122 | /** 123 | * Returns a string representation of the BitArray with bits 124 | * in mathemetical order. 125 | */ 126 | BitArray.prototype.toBinaryString = function () { 127 | return this.toArray().map(function (value) { 128 | return value ? '1' : '0'; 129 | }).reverse().join(''); 130 | }; 131 | 132 | /** 133 | * Returns a hexadecimal string representation of the BitArray 134 | * with bits in logical order. 135 | */ 136 | BitArray.prototype.toHexString = function () { 137 | var result = []; 138 | 139 | for (var i = 0; i < this.values.length; i += 1) { 140 | result.push(('00000000' + (this.values[i] >>> 0).toString(16)).slice(-8)); 141 | } 142 | return result.join(''); 143 | }; 144 | 145 | /** 146 | * Convert the BitArray to an Array of boolean values. 147 | */ 148 | BitArray.prototype.toArray = function () { 149 | var result = []; 150 | 151 | for (var i = 0; i < this.values.length * 32; i += 1) { 152 | result.push(this.get(i)); 153 | } 154 | return result; 155 | }; 156 | 157 | /** 158 | * Returns the total number of bits set to one in this BitArray. 159 | */ 160 | BitArray.prototype.count = function () { 161 | var total = 0; 162 | 163 | // If we remove the toggle method we could efficiently cache the number of bits without calculating it on the fly. 164 | this.values.forEach(function (x) { 165 | // See: http://bits.stephan-brumme.com/countBits.html for an explanation 166 | x = x - ((x >> 1) & 0x55555555); 167 | x = (x & 0x33333333) + ((x >> 2) & 0x33333333); 168 | x = x + (x >> 4); 169 | x &= 0xF0F0F0F; 170 | 171 | total += (x * 0x01010101) >> 24; 172 | }); 173 | return total; 174 | }; 175 | 176 | /** 177 | * Inverts this BitArray. 178 | */ 179 | BitArray.prototype.not = function () { 180 | this.values = this.values.map(function (v) { 181 | return ~v; 182 | }); 183 | return this; 184 | }; 185 | 186 | /** 187 | * Bitwise OR on the values of this BitArray using BitArray x. 188 | */ 189 | BitArray.prototype.or = function (x) { 190 | if (this.values.length !== x.values.length) { 191 | throw 'Arguments must be of the same length.'; 192 | } 193 | this.values = this.values.map(function (v, i) { 194 | return v | x.values[i]; 195 | }); 196 | return this; 197 | }; 198 | 199 | /** 200 | * Bitwise AND on the values of this BitArray using BitArray x. 201 | */ 202 | BitArray.prototype.and = function (x) { 203 | if (this.values.length !== x.values.length) { 204 | throw 'Arguments must be of the same length.'; 205 | } 206 | this.values = this.values.map(function (v, i) { 207 | return v & x.values[i]; 208 | }); 209 | return this; 210 | }; 211 | 212 | /** 213 | * Bitwise XOR on the values of this BitArray using BitArray x. 214 | */ 215 | BitArray.prototype.xor = function (x) { 216 | if (this.values.length !== x.values.length) { 217 | throw 'Arguments must be of the same length.'; 218 | } 219 | this.values = this.values.map(function (v, i) { 220 | return v ^ x.values[i]; 221 | }); 222 | return this; 223 | }; 224 | 225 | module.exports = BitArray; -------------------------------------------------------------------------------- /btc.js: -------------------------------------------------------------------------------- 1 | var Inventory = require("./btc/inventory.js") 2 | var Mempool = require("./btc/mempool.js") 3 | var Transactions = require("./btc/transactions.js") 4 | var Blockchain = require("./btc/blockchain.js") 5 | var Miner = require("./btc/miner.js") 6 | 7 | var btc = function(self) { 8 | new Inventory(self); 9 | new Mempool(self); 10 | new Transactions(self); 11 | new Blockchain(self); 12 | new Miner(self); 13 | }; 14 | 15 | 16 | btc.Blockchain = Blockchain.prototype; 17 | 18 | module.exports = btc; -------------------------------------------------------------------------------- /btc/blockchain.js: -------------------------------------------------------------------------------- 1 | /* 2 | btc-blockchain 3 | */ 4 | 5 | var colors = ["green", "orange", "blue", "purple", "brown", "steelblue", "red"] 6 | var color_i = 0; 7 | 8 | function Block(prev, time, miner) { 9 | this.__prev = prev; 10 | 11 | this.transactions = []; 12 | 13 | if (miner) { 14 | this.credit = miner.id; 15 | this.transactions = miner.mempool.getList(); 16 | } 17 | else 18 | this.credit = false; 19 | 20 | this.time = time; 21 | this.color = colors[++color_i % colors.length] 22 | 23 | if (prev) { 24 | this.id = miner.network.rand(); 25 | this.h = prev.h + 1; 26 | this.prev = prev.id; 27 | this.difficulty = prev.difficulty; 28 | this.work = prev.work + prev.difficulty; 29 | 30 | if (!(this.h % this.difficulty_adjustment_period)) { 31 | this.difficultyAdjustment() 32 | } 33 | } 34 | else { 35 | //this.id = String.fromCharCode(252, 124, 195, 233, 126, 94, 182, 200, 23, 107, 236, 43, 77, 137); 36 | this.id = 'xxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 37 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 38 | return v.toString(16); 39 | }); 40 | this.h = 0; 41 | this.prev = false; 42 | this.difficulty = 600000; 43 | this.work = 0; 44 | } 45 | } 46 | 47 | function PrevState(status) { 48 | this.status = status; 49 | 50 | this.equals = function(v) { 51 | return (v.status == this.status) 52 | } 53 | } 54 | 55 | var GenesisBlock = new Block(false, 0); 56 | 57 | Block.prototype = { 58 | target_avg_between_blocks: 10 * 60 * 1000, 59 | difficulty_adjustment_period: 2016, 60 | 61 | difficultyAdjustment: function() { 62 | var total = 0; 63 | var last = this.time; 64 | var cur = this._prev(); 65 | 66 | for (var i=0;i largestWork.work) { 208 | largestWork = subwork; 209 | } 210 | }) 211 | 212 | // return it 213 | return largestWork; 214 | } 215 | }, 216 | // this function helps identify orphan blocks 217 | reorg: function(block, numorphan, force) { 218 | var ourorphans = 0; 219 | if (numorphan == -1) { 220 | // This block couldn't be entered into the chainstate, so it's an orphan. 221 | if (!this.mapOrphans.is(block)) { 222 | this.mapOrphans.add(block) 223 | } else { 224 | return numorphan; 225 | } 226 | } 227 | 228 | // maybe it completes a chain though 229 | var cur = block; 230 | 231 | while(true) { 232 | var curprev = this.prevs.get(cur.id); 233 | if (curprev.status == "set") { 234 | var bestOrphanPath = this.getOrphanWorkPath(cur) 235 | if ((force && bestOrphanPath.work >= this.head.work) || bestOrphanPath.work > this.head.work) { 236 | //console.log(this.self.id + ": adopting orphan chain of (w=" + bestOrphanPath.work + " vs. local " + this.head.work + ")") 237 | ourorphans += this.enter(bestOrphanPath.end, true, true) 238 | } 239 | 240 | break; 241 | } else { 242 | cur = cur._prev(); 243 | } 244 | } 245 | if (numorphan == -1) { 246 | if (ourorphans == 0) 247 | return numorphan; 248 | else 249 | return ourorphans 250 | } 251 | else 252 | return numorphan + ourorphans; 253 | }, 254 | // enter a block into the chainstate, perhaps resulting in a reorg, and also perhaps resulting in its inclusion within maporphans 255 | enter: function(block, force, doingReorg) { 256 | //console.log((doingReorg ? "(reorg) " : "") + "entering new block at height " + block.h) 257 | if (block == this.head) 258 | return -1 259 | 260 | var bprev = this.prevs.get(block._prev().id) 261 | 262 | if (bprev.status == "none") { 263 | // this block's prev doesn't exist, it's an orphan! 264 | if (!doingReorg) 265 | return this.reorg(block, -1, force) 266 | } 267 | 268 | if (typeof force == "undefined") 269 | force = false; 270 | //else if (force) 271 | // this.self.log("\tchainstate forcefully entering branch") 272 | 273 | var numorphan = -1; 274 | 275 | if ((this.head.work < block.work) || force) { 276 | // the current head is now obsolete 277 | 278 | numorphan = 0; 279 | var forwards = [] 280 | var cur = block 281 | 282 | reorg: 283 | while(true) { 284 | if (cur.h > this.head.h) { 285 | forwards.push(cur) 286 | cur = cur._prev() 287 | } else if (cur == this.head) { 288 | while(true) { 289 | if (forwards.length > 0) { 290 | this.forward(forwards.pop()) 291 | } else { 292 | break reorg; 293 | } 294 | } 295 | } else { 296 | numorphan++; 297 | this.reverse() 298 | } 299 | } 300 | } else if (this.head.work == block.work) { 301 | //this.self.log("\tblock rejected; already seen one at this chainlength") 302 | } 303 | 304 | if (!doingReorg) 305 | numorphan = this.reorg(block, numorphan) 306 | 307 | return numorphan 308 | } 309 | } 310 | 311 | function Blockchain(self) { 312 | self.blockchain = this; 313 | 314 | this.chainstate = new Chainstate(GenesisBlock, self); 315 | 316 | this.newChainstate = function() { 317 | return new Chainstate(this.chainstate.head, self); 318 | } 319 | 320 | // When we receive a new block over the wire, process it here. 321 | this.onBlock = function(b) { 322 | if (this.chainstate.enter(b) != -1) { 323 | self.inventory.relay(b.id); 324 | return true; 325 | }; 326 | } 327 | 328 | self.on("obj:block", function(from, o) { 329 | if (this.onBlock(o)) 330 | self.handle(from, "blockchain:block", o); 331 | }, this) 332 | } 333 | 334 | Blockchain.prototype = { 335 | Block: Block, 336 | GenesisBlock: GenesisBlock 337 | } 338 | 339 | module.exports = Blockchain; -------------------------------------------------------------------------------- /btc/inventory.js: -------------------------------------------------------------------------------- 1 | /* 2 | btc-inventory 3 | 4 | Mimics the Bitcoin inventory system. 5 | */ 6 | 7 | function InventoryObject(type, obj) { 8 | this.type = type; 9 | this.obj = obj; 10 | this.id = obj.id; 11 | } 12 | 13 | function InventoryState(status) { 14 | this.status = status; 15 | 16 | this.equals = function(v) { 17 | return this.status == v.status; 18 | } 19 | } 20 | 21 | InventoryObject.prototype = { 22 | init: function(consensus) { 23 | consensus.add(this.id, this); 24 | }, 25 | 26 | seen: function(me) { 27 | var ir = me.get(this.id); 28 | 29 | if (ir.status == "none") { 30 | me.set(this.id, new InventoryState("seen")) 31 | 32 | return true; 33 | } 34 | 35 | return false; 36 | }, 37 | 38 | relay: function(me) { 39 | var ir = me.get(this.id); 40 | 41 | if (ir.status == "seen") { 42 | me.set(this.id, new InventoryState("relay")) 43 | 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | } 50 | 51 | function Inventory(self) { 52 | self.inventory = this; 53 | 54 | this.polling = false; 55 | 56 | this.objects = self.network.shared("inventory"); 57 | 58 | this.peerHas = {}; 59 | this.tellPeer = {}; 60 | this.mapAskFor = {}; 61 | this.mapAlreadyAskedFor = {}; 62 | 63 | this.addTick = function() { 64 | if (!this.polling) { 65 | this.polling = true; 66 | 67 | self.tick(1000, this.invTick, this) 68 | } 69 | } 70 | 71 | this.invTick = function() { 72 | var doneSomething = false; 73 | 74 | for (var p in this.tellPeer) { 75 | var invPacket = this.tellPeer[p]; 76 | 77 | if (Object.keys(invPacket).length != 0) { 78 | doneSomething = true; 79 | this.__send_inv(p, invPacket) 80 | 81 | this.tellPeer[p] = {}; // don't need to tell the peer that anymore 82 | } 83 | } 84 | 85 | var askMap = {}; 86 | for (var p in this.peerHas) { 87 | askMap[p] = []; 88 | } 89 | 90 | for (var p in this.mapAskFor) { 91 | for (name in this.mapAskFor[p]) { 92 | if (this.mapAskFor[p][name] <= self.now()) { 93 | askMap[p].push(name); 94 | delete this.mapAskFor[p][name] 95 | } 96 | } 97 | } 98 | 99 | for (var p in askMap) { 100 | if (askMap[p].length == 0) 101 | continue; 102 | 103 | doneSomething = true; 104 | 105 | this.__send_getdata(p, askMap[p]) 106 | } 107 | 108 | if (!doneSomething) { 109 | this.polling = false; // we don't need to poll again 110 | return false; // don't tick again 111 | } 112 | } 113 | 114 | /* 115 | p, {name1: type1, name2: type2, ...} 116 | */ 117 | this.__send_inv = function(p, mapNameTypes) { 118 | self.peermgr.send(p, "inv", mapNameTypes); 119 | } 120 | 121 | /* 122 | p, [name1, name2, name3] 123 | */ 124 | this.__send_getdata = function(p, askList) { 125 | self.peermgr.send(p, "getdata", askList); 126 | } 127 | 128 | /* 129 | p, (InventoryObject) o 130 | */ 131 | this.__send_invobj = function(p, o) { 132 | self.peermgr.send(p, "invobj", o); 133 | } 134 | 135 | this.relay = function(name, now) { 136 | var ir = this.objects.get(name); 137 | 138 | if ('relay' in ir) { 139 | if (ir.relay(this.objects)) { 140 | for (var p in this.tellPeer) { 141 | if (now) { 142 | this.__send_invobj(p, ir); 143 | } else { 144 | this.addTick(); 145 | this.tellPeer[p][name] = ir.type; 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | this.getObj = function(name, mustRelay) { 153 | var ir = this.objects.get(name); 154 | 155 | if (ir.status == "none") 156 | return false; 157 | 158 | if (mustRelay && ir.status == "seen") 159 | return false; 160 | 161 | return ir.__proto__; 162 | } 163 | 164 | this.onGetdata = function(from, msg) { 165 | msg.forEach(function(name) { 166 | if (o = this.getObj(name, true)) { 167 | this.__send_invobj(from, o); 168 | } 169 | }, this) 170 | } 171 | 172 | this.onInv = function(from, msg) { 173 | for (var name in msg) { 174 | // do we already have it? then we don't care 175 | if (this.getObj(name)) { 176 | // we already have it, so who cares 177 | } else { 178 | // start asking for it 179 | // and record who has it 180 | this.peerHas[from][name] = msg[name] 181 | 182 | if (!(name in this.mapAlreadyAskedFor)) { 183 | this.mapAlreadyAskedFor[name] = self.now(); 184 | } else { 185 | this.mapAlreadyAskedFor[name] += 2 * 60 * 1000; 186 | } 187 | this.mapAskFor[from][name] = this.mapAlreadyAskedFor[name]; 188 | this.addTick() 189 | } 190 | } 191 | } 192 | 193 | this.onInvobj = function(from, o) { 194 | // add it 195 | if (this.addObj(o)) { 196 | // stop asking other peers for it (if we are) 197 | delete this.mapAlreadyAskedFor[o.id] 198 | 199 | for (var p in this.mapAskFor) { 200 | delete this.mapAskFor[p][o.id] 201 | } 202 | 203 | // we no longer care that our peers have this object 204 | for (var p in this.peerHas) { 205 | delete this.peerHas[p][o.id] 206 | } 207 | 208 | // now run a handler 209 | self.handle(from, "obj:" + o.type, o.obj) 210 | } 211 | } 212 | 213 | this.addObj = function(obj) { 214 | return obj.seen(this.objects); 215 | } 216 | 217 | // obj must have `name` property 218 | this.createObj = function(type, obj) { 219 | var o = new InventoryObject(type, obj); 220 | 221 | this.objects.create(o); 222 | this.addObj(o); 223 | 224 | return o; 225 | } 226 | 227 | self.on("peermgr:connect", function(from) { 228 | var relaySet = this.objects.find({status:"relay"}); 229 | 230 | this.peerHas[from] = {}; 231 | this.tellPeer[from] = {}; 232 | this.mapAskFor[from] = {}; 233 | 234 | relaySet.forEach(function(o) { 235 | this.tellPeer[from][o.id] = o.type; 236 | }, this) 237 | 238 | this.addTick(); 239 | }, this) 240 | 241 | self.on("peermgr:disconnect", function(from) { 242 | delete this.peerHas[from] 243 | delete this.tellPeer[from] 244 | delete this.mapAlreadyAskedFor[from] 245 | delete this.mapAskFor[from]; 246 | }, this) 247 | 248 | self.on("inv", this.onInv, this) 249 | self.on("getdata", this.onGetdata, this) 250 | self.on("invobj", this.onInvobj, this) 251 | this.addTick(); 252 | } 253 | 254 | module.exports = Inventory; -------------------------------------------------------------------------------- /btc/mempool.js: -------------------------------------------------------------------------------- 1 | function PoolTxState(status) { 2 | this.status = status; 3 | 4 | this.equals = function(v) { 5 | return this.status == v.status; 6 | } 7 | } 8 | 9 | function PoolTx(tx) { 10 | this.tx = tx; 11 | } 12 | 13 | PoolTx.prototype = { 14 | init: function(consensus) { 15 | consensus.add(this.tx.id, this.tx); 16 | }, 17 | 18 | exists: function(me) { 19 | var ir = me.get(this.tx.id); 20 | 21 | if (ir.status == "none") { 22 | me.set(this.tx.id, new PoolTxState("exist")) 23 | 24 | return true; 25 | } 26 | 27 | return false; 28 | }, 29 | 30 | noexists: function(me) { 31 | var ir = me.get(this.tx.id); 32 | 33 | if (ir.status == "exist") { 34 | me.set(this.tx.id, new PoolTxState("none")) 35 | 36 | return true; 37 | } 38 | 39 | return false; 40 | } 41 | } 42 | 43 | function Mempool(self) { 44 | self.mempool = this; 45 | 46 | this.pool = self.network.shared("Mempool"); 47 | 48 | this.add = function(tx) { 49 | var n = new PoolTx(tx); 50 | this.pool.create(n); 51 | 52 | return n.exists(this.pool); 53 | } 54 | 55 | this.remove = function(tx) { 56 | var n = new PoolTx(tx); 57 | this.pool.create(n); 58 | 59 | return n.noexists(this.pool); 60 | } 61 | 62 | this.getList = function() { 63 | return this.pool.find({status:"exist"}); 64 | } 65 | } 66 | 67 | module.exports = Mempool; -------------------------------------------------------------------------------- /btc/miner.js: -------------------------------------------------------------------------------- 1 | function Miner(self) { 2 | self.miner = this; 3 | 4 | this.restage = function () { 5 | self.miner.difficulty = self.miner.staged.difficulty; 6 | if (self.miner.enabled) { 7 | self.miner.stopMining() 8 | self.miner.startMining() 9 | } 10 | } 11 | 12 | self.mprob = 0; 13 | this.mcb = function() { 14 | return new self.blockchain.Block(self.blockchain.chainstate.head, self.now(), self); 15 | } 16 | this.staged = false; 17 | this.enabled = false; 18 | this.difficulty = false; 19 | 20 | // expose mine() to NodeState 21 | self.mine = function(amt, cb) { 22 | this.mprob = amt; 23 | if (cb) 24 | self.miner.mcb = cb; 25 | 26 | self.miner.startMining(); 27 | } 28 | 29 | this.stopMining = function() { 30 | if (!this.enabled) 31 | return; 32 | 33 | this.enabled = false; 34 | self.deprob("mining") 35 | } 36 | 37 | this.startMining = function() { 38 | if (this.enabled) 39 | return; 40 | 41 | this.staged = this.mcb.call(self); // restage next block 42 | this.difficulty = this.staged.difficulty; 43 | this.enabled = true; 44 | 45 | if (self.mprob) { 46 | self.prob("mining", self.mprob / this.staged.difficulty, function() { 47 | self.handle(-1, "miner:success", this.staged); 48 | 49 | this.restage(); 50 | }, this) 51 | } 52 | } 53 | 54 | self.on("miner:success", function(from, b) { 55 | b.time = self.now(); 56 | b.transactions = self.mempool.getList(); 57 | 58 | self.inventory.createObj("block", b) 59 | 60 | if (self.blockchain.chainstate.enter(b) != -1) { 61 | self.inventory.relay(b.id, true); 62 | } 63 | }, this) 64 | 65 | self.on("blockchain:block", function(from, b) { 66 | this.restage(); 67 | }, this) 68 | } 69 | 70 | module.exports = Miner; -------------------------------------------------------------------------------- /btc/transactions.js: -------------------------------------------------------------------------------- 1 | /* 2 | btc-transactions 3 | */ 4 | 5 | function TxIn(ref, n) { 6 | this.ref = ref; 7 | this.n = n; 8 | this.k = ref.id + ':' + n; 9 | 10 | this.isvalid = function() {return true;} 11 | } 12 | 13 | function TxOut(txid, n) { 14 | this.n = n; 15 | this.k = txid + ':' + n; 16 | } 17 | 18 | function Transaction(id, inputs, n) { 19 | this.id = id; 20 | this.vin = inputs; 21 | this.vout = []; 22 | 23 | for (var i = 0;igoog.asserts.assert(foo) 21 | * will restrict foo to a truthy value. 22 | * 23 | * The compiler has an option to disable asserts. So code like: 24 | * 25 | * var x = goog.asserts.assert(foo()); goog.asserts.assert(bar()); 26 | * 27 | * will be transformed into: 28 | * 29 | * var x = foo(); 30 | * 31 | * The compiler will leave in foo() (because its return value is used), 32 | * but it will remove bar() because it assumes it does not have side-effects. 33 | * 34 | */ 35 | 36 | goog.provide('goog.asserts'); 37 | goog.provide('goog.asserts.AssertionError'); 38 | 39 | goog.require('goog.debug.Error'); 40 | goog.require('goog.dom.NodeType'); 41 | goog.require('goog.string'); 42 | 43 | 44 | /** 45 | * @define {boolean} Whether to strip out asserts or to leave them in. 46 | */ 47 | goog.define('goog.asserts.ENABLE_ASSERTS', goog.DEBUG); 48 | 49 | 50 | 51 | /** 52 | * Error object for failed assertions. 53 | * @param {string} messagePattern The pattern that was used to form message. 54 | * @param {!Array.<*>} messageArgs The items to substitute into the pattern. 55 | * @constructor 56 | * @extends {goog.debug.Error} 57 | * @final 58 | */ 59 | goog.asserts.AssertionError = function(messagePattern, messageArgs) { 60 | messageArgs.unshift(messagePattern); 61 | goog.debug.Error.call(this, goog.string.subs.apply(null, messageArgs)); 62 | // Remove the messagePattern afterwards to avoid permenantly modifying the 63 | // passed in array. 64 | messageArgs.shift(); 65 | 66 | /** 67 | * The message pattern used to format the error message. Error handlers can 68 | * use this to uniquely identify the assertion. 69 | * @type {string} 70 | */ 71 | this.messagePattern = messagePattern; 72 | }; 73 | goog.inherits(goog.asserts.AssertionError, goog.debug.Error); 74 | 75 | 76 | /** @override */ 77 | goog.asserts.AssertionError.prototype.name = 'AssertionError'; 78 | 79 | 80 | /** 81 | * Throws an exception with the given message and "Assertion failed" prefixed 82 | * onto it. 83 | * @param {string} defaultMessage The message to use if givenMessage is empty. 84 | * @param {Array.<*>} defaultArgs The substitution arguments for defaultMessage. 85 | * @param {string|undefined} givenMessage Message supplied by the caller. 86 | * @param {Array.<*>} givenArgs The substitution arguments for givenMessage. 87 | * @throws {goog.asserts.AssertionError} When the value is not a number. 88 | * @private 89 | */ 90 | goog.asserts.doAssertFailure_ = 91 | function(defaultMessage, defaultArgs, givenMessage, givenArgs) { 92 | var message = 'Assertion failed'; 93 | if (givenMessage) { 94 | message += ': ' + givenMessage; 95 | var args = givenArgs; 96 | } else if (defaultMessage) { 97 | message += ': ' + defaultMessage; 98 | args = defaultArgs; 99 | } 100 | // The '' + works around an Opera 10 bug in the unit tests. Without it, 101 | // a stack trace is added to var message above. With this, a stack trace is 102 | // not added until this line (it causes the extra garbage to be added after 103 | // the assertion message instead of in the middle of it). 104 | throw new goog.asserts.AssertionError('' + message, args || []); 105 | }; 106 | 107 | 108 | /** 109 | * Checks if the condition evaluates to true if goog.asserts.ENABLE_ASSERTS is 110 | * true. 111 | * @param {*} condition The condition to check. 112 | * @param {string=} opt_message Error message in case of failure. 113 | * @param {...*} var_args The items to substitute into the failure message. 114 | * @return {*} The value of the condition. 115 | * @throws {goog.asserts.AssertionError} When the condition evaluates to false. 116 | */ 117 | goog.asserts.assert = function(condition, opt_message, var_args) { 118 | if (goog.asserts.ENABLE_ASSERTS && !condition) { 119 | goog.asserts.doAssertFailure_('', null, opt_message, 120 | Array.prototype.slice.call(arguments, 2)); 121 | } 122 | return condition; 123 | }; 124 | 125 | 126 | /** 127 | * Fails if goog.asserts.ENABLE_ASSERTS is true. This function is useful in case 128 | * when we want to add a check in the unreachable area like switch-case 129 | * statement: 130 | * 131 | *
132 |  *  switch(type) {
133 |  *    case FOO: doSomething(); break;
134 |  *    case BAR: doSomethingElse(); break;
135 |  *    default: goog.assert.fail('Unrecognized type: ' + type);
136 |  *      // We have only 2 types - "default:" section is unreachable code.
137 |  *  }
138 |  * 
139 | * 140 | * @param {string=} opt_message Error message in case of failure. 141 | * @param {...*} var_args The items to substitute into the failure message. 142 | * @throws {goog.asserts.AssertionError} Failure. 143 | */ 144 | goog.asserts.fail = function(opt_message, var_args) { 145 | if (goog.asserts.ENABLE_ASSERTS) { 146 | throw new goog.asserts.AssertionError( 147 | 'Failure' + (opt_message ? ': ' + opt_message : ''), 148 | Array.prototype.slice.call(arguments, 1)); 149 | } 150 | }; 151 | 152 | 153 | /** 154 | * Checks if the value is a number if goog.asserts.ENABLE_ASSERTS is true. 155 | * @param {*} value The value to check. 156 | * @param {string=} opt_message Error message in case of failure. 157 | * @param {...*} var_args The items to substitute into the failure message. 158 | * @return {number} The value, guaranteed to be a number when asserts enabled. 159 | * @throws {goog.asserts.AssertionError} When the value is not a number. 160 | */ 161 | goog.asserts.assertNumber = function(value, opt_message, var_args) { 162 | if (goog.asserts.ENABLE_ASSERTS && !goog.isNumber(value)) { 163 | goog.asserts.doAssertFailure_('Expected number but got %s: %s.', 164 | [goog.typeOf(value), value], opt_message, 165 | Array.prototype.slice.call(arguments, 2)); 166 | } 167 | return /** @type {number} */ (value); 168 | }; 169 | 170 | 171 | /** 172 | * Checks if the value is a string if goog.asserts.ENABLE_ASSERTS is true. 173 | * @param {*} value The value to check. 174 | * @param {string=} opt_message Error message in case of failure. 175 | * @param {...*} var_args The items to substitute into the failure message. 176 | * @return {string} The value, guaranteed to be a string when asserts enabled. 177 | * @throws {goog.asserts.AssertionError} When the value is not a string. 178 | */ 179 | goog.asserts.assertString = function(value, opt_message, var_args) { 180 | if (goog.asserts.ENABLE_ASSERTS && !goog.isString(value)) { 181 | goog.asserts.doAssertFailure_('Expected string but got %s: %s.', 182 | [goog.typeOf(value), value], opt_message, 183 | Array.prototype.slice.call(arguments, 2)); 184 | } 185 | return /** @type {string} */ (value); 186 | }; 187 | 188 | 189 | /** 190 | * Checks if the value is a function if goog.asserts.ENABLE_ASSERTS is true. 191 | * @param {*} value The value to check. 192 | * @param {string=} opt_message Error message in case of failure. 193 | * @param {...*} var_args The items to substitute into the failure message. 194 | * @return {!Function} The value, guaranteed to be a function when asserts 195 | * enabled. 196 | * @throws {goog.asserts.AssertionError} When the value is not a function. 197 | */ 198 | goog.asserts.assertFunction = function(value, opt_message, var_args) { 199 | if (goog.asserts.ENABLE_ASSERTS && !goog.isFunction(value)) { 200 | goog.asserts.doAssertFailure_('Expected function but got %s: %s.', 201 | [goog.typeOf(value), value], opt_message, 202 | Array.prototype.slice.call(arguments, 2)); 203 | } 204 | return /** @type {!Function} */ (value); 205 | }; 206 | 207 | 208 | /** 209 | * Checks if the value is an Object if goog.asserts.ENABLE_ASSERTS is true. 210 | * @param {*} value The value to check. 211 | * @param {string=} opt_message Error message in case of failure. 212 | * @param {...*} var_args The items to substitute into the failure message. 213 | * @return {!Object} The value, guaranteed to be a non-null object. 214 | * @throws {goog.asserts.AssertionError} When the value is not an object. 215 | */ 216 | goog.asserts.assertObject = function(value, opt_message, var_args) { 217 | if (goog.asserts.ENABLE_ASSERTS && !goog.isObject(value)) { 218 | goog.asserts.doAssertFailure_('Expected object but got %s: %s.', 219 | [goog.typeOf(value), value], 220 | opt_message, Array.prototype.slice.call(arguments, 2)); 221 | } 222 | return /** @type {!Object} */ (value); 223 | }; 224 | 225 | 226 | /** 227 | * Checks if the value is an Array if goog.asserts.ENABLE_ASSERTS is true. 228 | * @param {*} value The value to check. 229 | * @param {string=} opt_message Error message in case of failure. 230 | * @param {...*} var_args The items to substitute into the failure message. 231 | * @return {!Array} The value, guaranteed to be a non-null array. 232 | * @throws {goog.asserts.AssertionError} When the value is not an array. 233 | */ 234 | goog.asserts.assertArray = function(value, opt_message, var_args) { 235 | if (goog.asserts.ENABLE_ASSERTS && !goog.isArray(value)) { 236 | goog.asserts.doAssertFailure_('Expected array but got %s: %s.', 237 | [goog.typeOf(value), value], opt_message, 238 | Array.prototype.slice.call(arguments, 2)); 239 | } 240 | return /** @type {!Array} */ (value); 241 | }; 242 | 243 | 244 | /** 245 | * Checks if the value is a boolean if goog.asserts.ENABLE_ASSERTS is true. 246 | * @param {*} value The value to check. 247 | * @param {string=} opt_message Error message in case of failure. 248 | * @param {...*} var_args The items to substitute into the failure message. 249 | * @return {boolean} The value, guaranteed to be a boolean when asserts are 250 | * enabled. 251 | * @throws {goog.asserts.AssertionError} When the value is not a boolean. 252 | */ 253 | goog.asserts.assertBoolean = function(value, opt_message, var_args) { 254 | if (goog.asserts.ENABLE_ASSERTS && !goog.isBoolean(value)) { 255 | goog.asserts.doAssertFailure_('Expected boolean but got %s: %s.', 256 | [goog.typeOf(value), value], opt_message, 257 | Array.prototype.slice.call(arguments, 2)); 258 | } 259 | return /** @type {boolean} */ (value); 260 | }; 261 | 262 | 263 | /** 264 | * Checks if the value is a DOM Element if goog.asserts.ENABLE_ASSERTS is true. 265 | * @param {*} value The value to check. 266 | * @param {string=} opt_message Error message in case of failure. 267 | * @param {...*} var_args The items to substitute into the failure message. 268 | * @return {!Element} The value, likely to be a DOM Element when asserts are 269 | * enabled. 270 | * @throws {goog.asserts.AssertionError} When the value is not a boolean. 271 | */ 272 | goog.asserts.assertElement = function(value, opt_message, var_args) { 273 | if (goog.asserts.ENABLE_ASSERTS && (!goog.isObject(value) || 274 | value.nodeType != goog.dom.NodeType.ELEMENT)) { 275 | goog.asserts.doAssertFailure_('Expected Element but got %s: %s.', 276 | [goog.typeOf(value), value], opt_message, 277 | Array.prototype.slice.call(arguments, 2)); 278 | } 279 | return /** @type {!Element} */ (value); 280 | }; 281 | 282 | 283 | /** 284 | * Checks if the value is an instance of the user-defined type if 285 | * goog.asserts.ENABLE_ASSERTS is true. 286 | * 287 | * The compiler may tighten the type returned by this function. 288 | * 289 | * @param {*} value The value to check. 290 | * @param {function(new: T, ...)} type A user-defined constructor. 291 | * @param {string=} opt_message Error message in case of failure. 292 | * @param {...*} var_args The items to substitute into the failure message. 293 | * @throws {goog.asserts.AssertionError} When the value is not an instance of 294 | * type. 295 | * @return {!T} 296 | * @template T 297 | */ 298 | goog.asserts.assertInstanceof = function(value, type, opt_message, var_args) { 299 | if (goog.asserts.ENABLE_ASSERTS && !(value instanceof type)) { 300 | goog.asserts.doAssertFailure_('instanceof check failed.', null, 301 | opt_message, Array.prototype.slice.call(arguments, 3)); 302 | } 303 | return value; 304 | }; 305 | 306 | 307 | /** 308 | * Checks that no enumerable keys are present in Object.prototype. Such keys 309 | * would break most code that use {@code for (var ... in ...)} loops. 310 | */ 311 | goog.asserts.assertObjectPrototypeIsIntact = function() { 312 | for (var key in Object.prototype) { 313 | goog.asserts.fail(key + ' should not be enumerable in Object.prototype.'); 314 | } 315 | }; 316 | -------------------------------------------------------------------------------- /goog/bootstrap/nodejs.js: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Closure Library Authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview A nodejs script for dynamically requiring Closure within 17 | * nodejs. 18 | * 19 | * Example of usage: 20 | * 21 | * require('./bootstrap/nodejs') 22 | * goog.require('goog.ui.Component') 23 | * 24 | * 25 | * This loads goog.ui.Component in the global scope. 26 | * 27 | * If you want to load custom libraries, you can require the custom deps file 28 | * directly. If your custom libraries introduce new globals, you may 29 | * need to run goog.nodeGlobalRequire to get them to load correctly. 30 | * 31 | * 32 | * require('./path/to/my/deps.js') 33 | * goog.bootstrap.nodeJs.nodeGlobalRequire('./path/to/my/base.js') 34 | * goog.require('my.Class') 35 | * 36 | * 37 | * @author nick@medium.com (Nick Santos) 38 | * 39 | * @nocompile 40 | */ 41 | 42 | 43 | var fs = require('fs'); 44 | var path = require('path'); 45 | 46 | 47 | /** 48 | * The goog namespace in the global scope. 49 | */ 50 | global.goog = {}; 51 | 52 | 53 | /** 54 | * Imports a script using Node's require() API. 55 | * 56 | * @param {string} src The script source. 57 | * @return {boolean} True if the script was imported, false otherwise. 58 | */ 59 | global.CLOSURE_IMPORT_SCRIPT = function(src) { 60 | // Sources are always expressed relative to closure's base.js, but 61 | // require() is always relative to the current source. 62 | require('./../' + src); 63 | return true; 64 | }; 65 | 66 | 67 | // Declared here so it can be used to require base.js 68 | function nodeGlobalRequire(file) { 69 | process.binding('evals').NodeScript.runInThisContext.call( 70 | global, fs.readFileSync(file), file); 71 | } 72 | 73 | 74 | // Load Closure's base.js into memory. It is assumed base.js is in the 75 | // directory above this directory given this script's location in 76 | // bootstrap/nodejs.js. 77 | nodeGlobalRequire(path.resolve(__dirname, '..', 'base.js')); 78 | 79 | 80 | /** 81 | * Bootstraps a file into the global scope. 82 | * 83 | * This is strictly for cases where normal require() won't work, 84 | * because the file declares global symbols with 'var' that need to 85 | * be added to the global scope. 86 | * @suppress {missingProvide} 87 | * 88 | * @param {string} file The path to the file. 89 | */ 90 | goog.nodeGlobalRequire = nodeGlobalRequire; 91 | 92 | -------------------------------------------------------------------------------- /goog/bootstrap/webworkers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview A bootstrap for dynamically requiring Closure within an HTML5 17 | * Web Worker context. To use this, first set CLOSURE_BASE_PATH to the directory 18 | * containing base.js (relative to the main script), then use importScripts to 19 | * load this file and base.js (in that order). After this you can use 20 | * goog.require for further imports. 21 | * 22 | * @nocompile 23 | */ 24 | 25 | 26 | /** 27 | * Imports a script using the Web Worker importScript API. 28 | * 29 | * @param {string} src The script source. 30 | * @return {boolean} True if the script was imported, false otherwise. 31 | */ 32 | this.CLOSURE_IMPORT_SCRIPT = (function(global) { 33 | return function(src) { 34 | global['importScripts'](src); 35 | return true; 36 | }; 37 | })(this); 38 | -------------------------------------------------------------------------------- /goog/debug/error.js: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Provides a base class for custom Error objects such that the 17 | * stack is correctly maintained. 18 | * 19 | * You should never need to throw goog.debug.Error(msg) directly, Error(msg) is 20 | * sufficient. 21 | * 22 | */ 23 | 24 | goog.provide('goog.debug.Error'); 25 | 26 | 27 | 28 | /** 29 | * Base class for custom error objects. 30 | * @param {*=} opt_msg The message associated with the error. 31 | * @constructor 32 | * @extends {Error} 33 | */ 34 | goog.debug.Error = function(opt_msg) { 35 | 36 | // Ensure there is a stack trace. 37 | if (Error.captureStackTrace) { 38 | Error.captureStackTrace(this, goog.debug.Error); 39 | } else { 40 | this.stack = new Error().stack || ''; 41 | } 42 | 43 | if (opt_msg) { 44 | this.message = String(opt_msg); 45 | } 46 | }; 47 | goog.inherits(goog.debug.Error, Error); 48 | 49 | 50 | /** @override */ 51 | goog.debug.Error.prototype.name = 'CustomError'; 52 | -------------------------------------------------------------------------------- /goog/dom/nodetype.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Definition of goog.dom.NodeType. 17 | */ 18 | 19 | goog.provide('goog.dom.NodeType'); 20 | 21 | 22 | /** 23 | * Constants for the nodeType attribute in the Node interface. 24 | * 25 | * These constants match those specified in the Node interface. These are 26 | * usually present on the Node object in recent browsers, but not in older 27 | * browsers (specifically, early IEs) and thus are given here. 28 | * 29 | * In some browsers (early IEs), these are not defined on the Node object, 30 | * so they are provided here. 31 | * 32 | * See http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-1950641247 33 | * @enum {number} 34 | */ 35 | goog.dom.NodeType = { 36 | ELEMENT: 1, 37 | ATTRIBUTE: 2, 38 | TEXT: 3, 39 | CDATA_SECTION: 4, 40 | ENTITY_REFERENCE: 5, 41 | ENTITY: 6, 42 | PROCESSING_INSTRUCTION: 7, 43 | COMMENT: 8, 44 | DOCUMENT: 9, 45 | DOCUMENT_TYPE: 10, 46 | DOCUMENT_FRAGMENT: 11, 47 | NOTATION: 12 48 | }; 49 | -------------------------------------------------------------------------------- /goog/object/object.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Utilities for manipulating objects/maps/hashes. 17 | */ 18 | 19 | goog.provide('goog.object'); 20 | 21 | 22 | /** 23 | * Calls a function for each element in an object/map/hash. 24 | * 25 | * @param {Object.} obj The object over which to iterate. 26 | * @param {function(this:T,V,?,Object.):?} f The function to call 27 | * for every element. This function takes 3 arguments (the element, the 28 | * index and the object) and the return value is ignored. 29 | * @param {T=} opt_obj This is used as the 'this' object within f. 30 | * @template T,K,V 31 | */ 32 | goog.object.forEach = function(obj, f, opt_obj) { 33 | for (var key in obj) { 34 | f.call(opt_obj, obj[key], key, obj); 35 | } 36 | }; 37 | 38 | 39 | /** 40 | * Calls a function for each element in an object/map/hash. If that call returns 41 | * true, adds the element to a new object. 42 | * 43 | * @param {Object.} obj The object over which to iterate. 44 | * @param {function(this:T,V,?,Object.):boolean} f The function to call 45 | * for every element. This 46 | * function takes 3 arguments (the element, the index and the object) 47 | * and should return a boolean. If the return value is true the 48 | * element is added to the result object. If it is false the 49 | * element is not included. 50 | * @param {T=} opt_obj This is used as the 'this' object within f. 51 | * @return {!Object.} a new object in which only elements that passed the 52 | * test are present. 53 | * @template T,K,V 54 | */ 55 | goog.object.filter = function(obj, f, opt_obj) { 56 | var res = {}; 57 | for (var key in obj) { 58 | if (f.call(opt_obj, obj[key], key, obj)) { 59 | res[key] = obj[key]; 60 | } 61 | } 62 | return res; 63 | }; 64 | 65 | 66 | /** 67 | * For every element in an object/map/hash calls a function and inserts the 68 | * result into a new object. 69 | * 70 | * @param {Object.} obj The object over which to iterate. 71 | * @param {function(this:T,V,?,Object.):R} f The function to call 72 | * for every element. This function 73 | * takes 3 arguments (the element, the index and the object) 74 | * and should return something. The result will be inserted 75 | * into a new object. 76 | * @param {T=} opt_obj This is used as the 'this' object within f. 77 | * @return {!Object.} a new object with the results from f. 78 | * @template T,K,V,R 79 | */ 80 | goog.object.map = function(obj, f, opt_obj) { 81 | var res = {}; 82 | for (var key in obj) { 83 | res[key] = f.call(opt_obj, obj[key], key, obj); 84 | } 85 | return res; 86 | }; 87 | 88 | 89 | /** 90 | * Calls a function for each element in an object/map/hash. If any 91 | * call returns true, returns true (without checking the rest). If 92 | * all calls return false, returns false. 93 | * 94 | * @param {Object.} obj The object to check. 95 | * @param {function(this:T,V,?,Object.):boolean} f The function to 96 | * call for every element. This function 97 | * takes 3 arguments (the element, the index and the object) and should 98 | * return a boolean. 99 | * @param {T=} opt_obj This is used as the 'this' object within f. 100 | * @return {boolean} true if any element passes the test. 101 | * @template T,K,V 102 | */ 103 | goog.object.some = function(obj, f, opt_obj) { 104 | for (var key in obj) { 105 | if (f.call(opt_obj, obj[key], key, obj)) { 106 | return true; 107 | } 108 | } 109 | return false; 110 | }; 111 | 112 | 113 | /** 114 | * Calls a function for each element in an object/map/hash. If 115 | * all calls return true, returns true. If any call returns false, returns 116 | * false at this point and does not continue to check the remaining elements. 117 | * 118 | * @param {Object.} obj The object to check. 119 | * @param {?function(this:T,V,?,Object.):boolean} f The function to 120 | * call for every element. This function 121 | * takes 3 arguments (the element, the index and the object) and should 122 | * return a boolean. 123 | * @param {T=} opt_obj This is used as the 'this' object within f. 124 | * @return {boolean} false if any element fails the test. 125 | * @template T,K,V 126 | */ 127 | goog.object.every = function(obj, f, opt_obj) { 128 | for (var key in obj) { 129 | if (!f.call(opt_obj, obj[key], key, obj)) { 130 | return false; 131 | } 132 | } 133 | return true; 134 | }; 135 | 136 | 137 | /** 138 | * Returns the number of key-value pairs in the object map. 139 | * 140 | * @param {Object} obj The object for which to get the number of key-value 141 | * pairs. 142 | * @return {number} The number of key-value pairs in the object map. 143 | */ 144 | goog.object.getCount = function(obj) { 145 | // JS1.5 has __count__ but it has been deprecated so it raises a warning... 146 | // in other words do not use. Also __count__ only includes the fields on the 147 | // actual object and not in the prototype chain. 148 | var rv = 0; 149 | for (var key in obj) { 150 | rv++; 151 | } 152 | return rv; 153 | }; 154 | 155 | 156 | /** 157 | * Returns one key from the object map, if any exists. 158 | * For map literals the returned key will be the first one in most of the 159 | * browsers (a know exception is Konqueror). 160 | * 161 | * @param {Object} obj The object to pick a key from. 162 | * @return {string|undefined} The key or undefined if the object is empty. 163 | */ 164 | goog.object.getAnyKey = function(obj) { 165 | for (var key in obj) { 166 | return key; 167 | } 168 | }; 169 | 170 | 171 | /** 172 | * Returns one value from the object map, if any exists. 173 | * For map literals the returned value will be the first one in most of the 174 | * browsers (a know exception is Konqueror). 175 | * 176 | * @param {Object.} obj The object to pick a value from. 177 | * @return {V|undefined} The value or undefined if the object is empty. 178 | * @template K,V 179 | */ 180 | goog.object.getAnyValue = function(obj) { 181 | for (var key in obj) { 182 | return obj[key]; 183 | } 184 | }; 185 | 186 | 187 | /** 188 | * Whether the object/hash/map contains the given object as a value. 189 | * An alias for goog.object.containsValue(obj, val). 190 | * 191 | * @param {Object.} obj The object in which to look for val. 192 | * @param {V} val The object for which to check. 193 | * @return {boolean} true if val is present. 194 | * @template K,V 195 | */ 196 | goog.object.contains = function(obj, val) { 197 | return goog.object.containsValue(obj, val); 198 | }; 199 | 200 | 201 | /** 202 | * Returns the values of the object/map/hash. 203 | * 204 | * @param {Object.} obj The object from which to get the values. 205 | * @return {!Array.} The values in the object/map/hash. 206 | * @template K,V 207 | */ 208 | goog.object.getValues = function(obj) { 209 | var res = []; 210 | var i = 0; 211 | for (var key in obj) { 212 | res[i++] = obj[key]; 213 | } 214 | return res; 215 | }; 216 | 217 | 218 | /** 219 | * Returns the keys of the object/map/hash. 220 | * 221 | * @param {Object} obj The object from which to get the keys. 222 | * @return {!Array.} Array of property keys. 223 | */ 224 | goog.object.getKeys = function(obj) { 225 | var res = []; 226 | var i = 0; 227 | for (var key in obj) { 228 | res[i++] = key; 229 | } 230 | return res; 231 | }; 232 | 233 | 234 | /** 235 | * Get a value from an object multiple levels deep. This is useful for 236 | * pulling values from deeply nested objects, such as JSON responses. 237 | * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3) 238 | * 239 | * @param {!Object} obj An object to get the value from. Can be array-like. 240 | * @param {...(string|number|!Array.)} var_args A number of keys 241 | * (as strings, or numbers, for array-like objects). Can also be 242 | * specified as a single array of keys. 243 | * @return {*} The resulting value. If, at any point, the value for a key 244 | * is undefined, returns undefined. 245 | */ 246 | goog.object.getValueByKeys = function(obj, var_args) { 247 | var isArrayLike = goog.isArrayLike(var_args); 248 | var keys = isArrayLike ? var_args : arguments; 249 | 250 | // Start with the 2nd parameter for the variable parameters syntax. 251 | for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) { 252 | obj = obj[keys[i]]; 253 | if (!goog.isDef(obj)) { 254 | break; 255 | } 256 | } 257 | 258 | return obj; 259 | }; 260 | 261 | 262 | /** 263 | * Whether the object/map/hash contains the given key. 264 | * 265 | * @param {Object} obj The object in which to look for key. 266 | * @param {*} key The key for which to check. 267 | * @return {boolean} true If the map contains the key. 268 | */ 269 | goog.object.containsKey = function(obj, key) { 270 | return key in obj; 271 | }; 272 | 273 | 274 | /** 275 | * Whether the object/map/hash contains the given value. This is O(n). 276 | * 277 | * @param {Object.} obj The object in which to look for val. 278 | * @param {V} val The value for which to check. 279 | * @return {boolean} true If the map contains the value. 280 | * @template K,V 281 | */ 282 | goog.object.containsValue = function(obj, val) { 283 | for (var key in obj) { 284 | if (obj[key] == val) { 285 | return true; 286 | } 287 | } 288 | return false; 289 | }; 290 | 291 | 292 | /** 293 | * Searches an object for an element that satisfies the given condition and 294 | * returns its key. 295 | * @param {Object.} obj The object to search in. 296 | * @param {function(this:T,V,string,Object.):boolean} f The 297 | * function to call for every element. Takes 3 arguments (the value, 298 | * the key and the object) and should return a boolean. 299 | * @param {T=} opt_this An optional "this" context for the function. 300 | * @return {string|undefined} The key of an element for which the function 301 | * returns true or undefined if no such element is found. 302 | * @template T,K,V 303 | */ 304 | goog.object.findKey = function(obj, f, opt_this) { 305 | for (var key in obj) { 306 | if (f.call(opt_this, obj[key], key, obj)) { 307 | return key; 308 | } 309 | } 310 | return undefined; 311 | }; 312 | 313 | 314 | /** 315 | * Searches an object for an element that satisfies the given condition and 316 | * returns its value. 317 | * @param {Object.} obj The object to search in. 318 | * @param {function(this:T,V,string,Object.):boolean} f The function 319 | * to call for every element. Takes 3 arguments (the value, the key 320 | * and the object) and should return a boolean. 321 | * @param {T=} opt_this An optional "this" context for the function. 322 | * @return {V} The value of an element for which the function returns true or 323 | * undefined if no such element is found. 324 | * @template T,K,V 325 | */ 326 | goog.object.findValue = function(obj, f, opt_this) { 327 | var key = goog.object.findKey(obj, f, opt_this); 328 | return key && obj[key]; 329 | }; 330 | 331 | 332 | /** 333 | * Whether the object/map/hash is empty. 334 | * 335 | * @param {Object} obj The object to test. 336 | * @return {boolean} true if obj is empty. 337 | */ 338 | goog.object.isEmpty = function(obj) { 339 | for (var key in obj) { 340 | return false; 341 | } 342 | return true; 343 | }; 344 | 345 | 346 | /** 347 | * Removes all key value pairs from the object/map/hash. 348 | * 349 | * @param {Object} obj The object to clear. 350 | */ 351 | goog.object.clear = function(obj) { 352 | for (var i in obj) { 353 | delete obj[i]; 354 | } 355 | }; 356 | 357 | 358 | /** 359 | * Removes a key-value pair based on the key. 360 | * 361 | * @param {Object} obj The object from which to remove the key. 362 | * @param {*} key The key to remove. 363 | * @return {boolean} Whether an element was removed. 364 | */ 365 | goog.object.remove = function(obj, key) { 366 | var rv; 367 | if ((rv = key in obj)) { 368 | delete obj[key]; 369 | } 370 | return rv; 371 | }; 372 | 373 | 374 | /** 375 | * Adds a key-value pair to the object. Throws an exception if the key is 376 | * already in use. Use set if you want to change an existing pair. 377 | * 378 | * @param {Object.} obj The object to which to add the key-value pair. 379 | * @param {string} key The key to add. 380 | * @param {V} val The value to add. 381 | * @template K,V 382 | */ 383 | goog.object.add = function(obj, key, val) { 384 | if (key in obj) { 385 | throw Error('The object already contains the key "' + key + '"'); 386 | } 387 | goog.object.set(obj, key, val); 388 | }; 389 | 390 | 391 | /** 392 | * Returns the value for the given key. 393 | * 394 | * @param {Object.} obj The object from which to get the value. 395 | * @param {string} key The key for which to get the value. 396 | * @param {R=} opt_val The value to return if no item is found for the given 397 | * key (default is undefined). 398 | * @return {V|R|undefined} The value for the given key. 399 | * @template K,V,R 400 | */ 401 | goog.object.get = function(obj, key, opt_val) { 402 | if (key in obj) { 403 | return obj[key]; 404 | } 405 | return opt_val; 406 | }; 407 | 408 | 409 | /** 410 | * Adds a key-value pair to the object/map/hash. 411 | * 412 | * @param {Object.} obj The object to which to add the key-value pair. 413 | * @param {string} key The key to add. 414 | * @param {V} value The value to add. 415 | * @template K,V 416 | */ 417 | goog.object.set = function(obj, key, value) { 418 | obj[key] = value; 419 | }; 420 | 421 | 422 | /** 423 | * Adds a key-value pair to the object/map/hash if it doesn't exist yet. 424 | * 425 | * @param {Object.} obj The object to which to add the key-value pair. 426 | * @param {string} key The key to add. 427 | * @param {V} value The value to add if the key wasn't present. 428 | * @return {V} The value of the entry at the end of the function. 429 | * @template K,V 430 | */ 431 | goog.object.setIfUndefined = function(obj, key, value) { 432 | return key in obj ? obj[key] : (obj[key] = value); 433 | }; 434 | 435 | 436 | /** 437 | * Does a flat clone of the object. 438 | * 439 | * @param {Object.} obj Object to clone. 440 | * @return {!Object.} Clone of the input object. 441 | * @template K,V 442 | */ 443 | goog.object.clone = function(obj) { 444 | // We cannot use the prototype trick because a lot of methods depend on where 445 | // the actual key is set. 446 | 447 | var res = {}; 448 | for (var key in obj) { 449 | res[key] = obj[key]; 450 | } 451 | return res; 452 | // We could also use goog.mixin but I wanted this to be independent from that. 453 | }; 454 | 455 | 456 | /** 457 | * Clones a value. The input may be an Object, Array, or basic type. Objects and 458 | * arrays will be cloned recursively. 459 | * 460 | * WARNINGS: 461 | * goog.object.unsafeClone does not detect reference loops. Objects 462 | * that refer to themselves will cause infinite recursion. 463 | * 464 | * goog.object.unsafeClone is unaware of unique identifiers, and 465 | * copies UIDs created by getUid into cloned results. 466 | * 467 | * @param {*} obj The value to clone. 468 | * @return {*} A clone of the input value. 469 | */ 470 | goog.object.unsafeClone = function(obj) { 471 | var type = goog.typeOf(obj); 472 | if (type == 'object' || type == 'array') { 473 | if (obj.clone) { 474 | return obj.clone(); 475 | } 476 | var clone = type == 'array' ? [] : {}; 477 | for (var key in obj) { 478 | clone[key] = goog.object.unsafeClone(obj[key]); 479 | } 480 | return clone; 481 | } 482 | 483 | return obj; 484 | }; 485 | 486 | 487 | /** 488 | * Returns a new object in which all the keys and values are interchanged 489 | * (keys become values and values become keys). If multiple keys map to the 490 | * same value, the chosen transposed value is implementation-dependent. 491 | * 492 | * @param {Object} obj The object to transpose. 493 | * @return {!Object} The transposed object. 494 | */ 495 | goog.object.transpose = function(obj) { 496 | var transposed = {}; 497 | for (var key in obj) { 498 | transposed[obj[key]] = key; 499 | } 500 | return transposed; 501 | }; 502 | 503 | 504 | /** 505 | * The names of the fields that are defined on Object.prototype. 506 | * @type {Array.} 507 | * @private 508 | */ 509 | goog.object.PROTOTYPE_FIELDS_ = [ 510 | 'constructor', 511 | 'hasOwnProperty', 512 | 'isPrototypeOf', 513 | 'propertyIsEnumerable', 514 | 'toLocaleString', 515 | 'toString', 516 | 'valueOf' 517 | ]; 518 | 519 | 520 | /** 521 | * Extends an object with another object. 522 | * This operates 'in-place'; it does not create a new Object. 523 | * 524 | * Example: 525 | * var o = {}; 526 | * goog.object.extend(o, {a: 0, b: 1}); 527 | * o; // {a: 0, b: 1} 528 | * goog.object.extend(o, {c: 2}); 529 | * o; // {a: 0, b: 1, c: 2} 530 | * 531 | * @param {Object} target The object to modify. 532 | * @param {...Object} var_args The objects from which values will be copied. 533 | */ 534 | goog.object.extend = function(target, var_args) { 535 | var key, source; 536 | for (var i = 1; i < arguments.length; i++) { 537 | source = arguments[i]; 538 | for (key in source) { 539 | target[key] = source[key]; 540 | } 541 | 542 | // For IE the for-in-loop does not contain any properties that are not 543 | // enumerable on the prototype object (for example isPrototypeOf from 544 | // Object.prototype) and it will also not include 'replace' on objects that 545 | // extend String and change 'replace' (not that it is common for anyone to 546 | // extend anything except Object). 547 | 548 | for (var j = 0; j < goog.object.PROTOTYPE_FIELDS_.length; j++) { 549 | key = goog.object.PROTOTYPE_FIELDS_[j]; 550 | if (Object.prototype.hasOwnProperty.call(source, key)) { 551 | target[key] = source[key]; 552 | } 553 | } 554 | } 555 | }; 556 | 557 | 558 | /** 559 | * Creates a new object built from the key-value pairs provided as arguments. 560 | * @param {...*} var_args If only one argument is provided and it is an array 561 | * then this is used as the arguments, otherwise even arguments are used as 562 | * the property names and odd arguments are used as the property values. 563 | * @return {!Object} The new object. 564 | * @throws {Error} If there are uneven number of arguments or there is only one 565 | * non array argument. 566 | */ 567 | goog.object.create = function(var_args) { 568 | var argLength = arguments.length; 569 | if (argLength == 1 && goog.isArray(arguments[0])) { 570 | return goog.object.create.apply(null, arguments[0]); 571 | } 572 | 573 | if (argLength % 2) { 574 | throw Error('Uneven number of arguments'); 575 | } 576 | 577 | var rv = {}; 578 | for (var i = 0; i < argLength; i += 2) { 579 | rv[arguments[i]] = arguments[i + 1]; 580 | } 581 | return rv; 582 | }; 583 | 584 | 585 | /** 586 | * Creates a new object where the property names come from the arguments but 587 | * the value is always set to true 588 | * @param {...*} var_args If only one argument is provided and it is an array 589 | * then this is used as the arguments, otherwise the arguments are used 590 | * as the property names. 591 | * @return {!Object} The new object. 592 | */ 593 | goog.object.createSet = function(var_args) { 594 | var argLength = arguments.length; 595 | if (argLength == 1 && goog.isArray(arguments[0])) { 596 | return goog.object.createSet.apply(null, arguments[0]); 597 | } 598 | 599 | var rv = {}; 600 | for (var i = 0; i < argLength; i++) { 601 | rv[arguments[i]] = true; 602 | } 603 | return rv; 604 | }; 605 | 606 | 607 | /** 608 | * Creates an immutable view of the underlying object, if the browser 609 | * supports immutable objects. 610 | * 611 | * In default mode, writes to this view will fail silently. In strict mode, 612 | * they will throw an error. 613 | * 614 | * @param {!Object.} obj An object. 615 | * @return {!Object.} An immutable view of that object, or the 616 | * original object if this browser does not support immutables. 617 | * @template K,V 618 | */ 619 | goog.object.createImmutableView = function(obj) { 620 | var result = obj; 621 | if (Object.isFrozen && !Object.isFrozen(obj)) { 622 | result = Object.create(obj); 623 | Object.freeze(result); 624 | } 625 | return result; 626 | }; 627 | 628 | 629 | /** 630 | * @param {!Object} obj An object. 631 | * @return {boolean} Whether this is an immutable view of the object. 632 | */ 633 | goog.object.isImmutableView = function(obj) { 634 | return !!Object.isFrozen && Object.isFrozen(obj); 635 | }; 636 | -------------------------------------------------------------------------------- /goog/string/string.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Utilities for string manipulation. 17 | */ 18 | 19 | 20 | /** 21 | * Namespace for string utilities 22 | */ 23 | goog.provide('goog.string'); 24 | goog.provide('goog.string.Unicode'); 25 | 26 | 27 | /** 28 | * Common Unicode string characters. 29 | * @enum {string} 30 | */ 31 | goog.string.Unicode = { 32 | NBSP: '\xa0' 33 | }; 34 | 35 | 36 | /** 37 | * Fast prefix-checker. 38 | * @param {string} str The string to check. 39 | * @param {string} prefix A string to look for at the start of {@code str}. 40 | * @return {boolean} True if {@code str} begins with {@code prefix}. 41 | */ 42 | goog.string.startsWith = function(str, prefix) { 43 | return str.lastIndexOf(prefix, 0) == 0; 44 | }; 45 | 46 | 47 | /** 48 | * Fast suffix-checker. 49 | * @param {string} str The string to check. 50 | * @param {string} suffix A string to look for at the end of {@code str}. 51 | * @return {boolean} True if {@code str} ends with {@code suffix}. 52 | */ 53 | goog.string.endsWith = function(str, suffix) { 54 | var l = str.length - suffix.length; 55 | return l >= 0 && str.indexOf(suffix, l) == l; 56 | }; 57 | 58 | 59 | /** 60 | * Case-insensitive prefix-checker. 61 | * @param {string} str The string to check. 62 | * @param {string} prefix A string to look for at the end of {@code str}. 63 | * @return {boolean} True if {@code str} begins with {@code prefix} (ignoring 64 | * case). 65 | */ 66 | goog.string.caseInsensitiveStartsWith = function(str, prefix) { 67 | return goog.string.caseInsensitiveCompare( 68 | prefix, str.substr(0, prefix.length)) == 0; 69 | }; 70 | 71 | 72 | /** 73 | * Case-insensitive suffix-checker. 74 | * @param {string} str The string to check. 75 | * @param {string} suffix A string to look for at the end of {@code str}. 76 | * @return {boolean} True if {@code str} ends with {@code suffix} (ignoring 77 | * case). 78 | */ 79 | goog.string.caseInsensitiveEndsWith = function(str, suffix) { 80 | return goog.string.caseInsensitiveCompare( 81 | suffix, str.substr(str.length - suffix.length, suffix.length)) == 0; 82 | }; 83 | 84 | 85 | /** 86 | * Case-insensitive equality checker. 87 | * @param {string} str1 First string to check. 88 | * @param {string} str2 Second string to check. 89 | * @return {boolean} True if {@code str1} and {@code str2} are the same string, 90 | * ignoring case. 91 | */ 92 | goog.string.caseInsensitiveEquals = function(str1, str2) { 93 | return str1.toLowerCase() == str2.toLowerCase(); 94 | }; 95 | 96 | 97 | /** 98 | * Does simple python-style string substitution. 99 | * subs("foo%s hot%s", "bar", "dog") becomes "foobar hotdog". 100 | * @param {string} str The string containing the pattern. 101 | * @param {...*} var_args The items to substitute into the pattern. 102 | * @return {string} A copy of {@code str} in which each occurrence of 103 | * {@code %s} has been replaced an argument from {@code var_args}. 104 | */ 105 | goog.string.subs = function(str, var_args) { 106 | var splitParts = str.split('%s'); 107 | var returnString = ''; 108 | 109 | var subsArguments = Array.prototype.slice.call(arguments, 1); 110 | while (subsArguments.length && 111 | // Replace up to the last split part. We are inserting in the 112 | // positions between split parts. 113 | splitParts.length > 1) { 114 | returnString += splitParts.shift() + subsArguments.shift(); 115 | } 116 | 117 | return returnString + splitParts.join('%s'); // Join unused '%s' 118 | }; 119 | 120 | 121 | /** 122 | * Converts multiple whitespace chars (spaces, non-breaking-spaces, new lines 123 | * and tabs) to a single space, and strips leading and trailing whitespace. 124 | * @param {string} str Input string. 125 | * @return {string} A copy of {@code str} with collapsed whitespace. 126 | */ 127 | goog.string.collapseWhitespace = function(str) { 128 | // Since IE doesn't include non-breaking-space (0xa0) in their \s character 129 | // class (as required by section 7.2 of the ECMAScript spec), we explicitly 130 | // include it in the regexp to enforce consistent cross-browser behavior. 131 | return str.replace(/[\s\xa0]+/g, ' ').replace(/^\s+|\s+$/g, ''); 132 | }; 133 | 134 | 135 | /** 136 | * Checks if a string is empty or contains only whitespaces. 137 | * @param {string} str The string to check. 138 | * @return {boolean} True if {@code str} is empty or whitespace only. 139 | */ 140 | goog.string.isEmpty = function(str) { 141 | // testing length == 0 first is actually slower in all browsers (about the 142 | // same in Opera). 143 | // Since IE doesn't include non-breaking-space (0xa0) in their \s character 144 | // class (as required by section 7.2 of the ECMAScript spec), we explicitly 145 | // include it in the regexp to enforce consistent cross-browser behavior. 146 | return /^[\s\xa0]*$/.test(str); 147 | }; 148 | 149 | 150 | /** 151 | * Checks if a string is null, undefined, empty or contains only whitespaces. 152 | * @param {*} str The string to check. 153 | * @return {boolean} True if{@code str} is null, undefined, empty, or 154 | * whitespace only. 155 | */ 156 | goog.string.isEmptySafe = function(str) { 157 | return goog.string.isEmpty(goog.string.makeSafe(str)); 158 | }; 159 | 160 | 161 | /** 162 | * Checks if a string is all breaking whitespace. 163 | * @param {string} str The string to check. 164 | * @return {boolean} Whether the string is all breaking whitespace. 165 | */ 166 | goog.string.isBreakingWhitespace = function(str) { 167 | return !/[^\t\n\r ]/.test(str); 168 | }; 169 | 170 | 171 | /** 172 | * Checks if a string contains all letters. 173 | * @param {string} str string to check. 174 | * @return {boolean} True if {@code str} consists entirely of letters. 175 | */ 176 | goog.string.isAlpha = function(str) { 177 | return !/[^a-zA-Z]/.test(str); 178 | }; 179 | 180 | 181 | /** 182 | * Checks if a string contains only numbers. 183 | * @param {*} str string to check. If not a string, it will be 184 | * casted to one. 185 | * @return {boolean} True if {@code str} is numeric. 186 | */ 187 | goog.string.isNumeric = function(str) { 188 | return !/[^0-9]/.test(str); 189 | }; 190 | 191 | 192 | /** 193 | * Checks if a string contains only numbers or letters. 194 | * @param {string} str string to check. 195 | * @return {boolean} True if {@code str} is alphanumeric. 196 | */ 197 | goog.string.isAlphaNumeric = function(str) { 198 | return !/[^a-zA-Z0-9]/.test(str); 199 | }; 200 | 201 | 202 | /** 203 | * Checks if a character is a space character. 204 | * @param {string} ch Character to check. 205 | * @return {boolean} True if {code ch} is a space. 206 | */ 207 | goog.string.isSpace = function(ch) { 208 | return ch == ' '; 209 | }; 210 | 211 | 212 | /** 213 | * Checks if a character is a valid unicode character. 214 | * @param {string} ch Character to check. 215 | * @return {boolean} True if {code ch} is a valid unicode character. 216 | */ 217 | goog.string.isUnicodeChar = function(ch) { 218 | return ch.length == 1 && ch >= ' ' && ch <= '~' || 219 | ch >= '\u0080' && ch <= '\uFFFD'; 220 | }; 221 | 222 | 223 | /** 224 | * Takes a string and replaces newlines with a space. Multiple lines are 225 | * replaced with a single space. 226 | * @param {string} str The string from which to strip newlines. 227 | * @return {string} A copy of {@code str} stripped of newlines. 228 | */ 229 | goog.string.stripNewlines = function(str) { 230 | return str.replace(/(\r\n|\r|\n)+/g, ' '); 231 | }; 232 | 233 | 234 | /** 235 | * Replaces Windows and Mac new lines with unix style: \r or \r\n with \n. 236 | * @param {string} str The string to in which to canonicalize newlines. 237 | * @return {string} {@code str} A copy of {@code} with canonicalized newlines. 238 | */ 239 | goog.string.canonicalizeNewlines = function(str) { 240 | return str.replace(/(\r\n|\r|\n)/g, '\n'); 241 | }; 242 | 243 | 244 | /** 245 | * Normalizes whitespace in a string, replacing all whitespace chars with 246 | * a space. 247 | * @param {string} str The string in which to normalize whitespace. 248 | * @return {string} A copy of {@code str} with all whitespace normalized. 249 | */ 250 | goog.string.normalizeWhitespace = function(str) { 251 | return str.replace(/\xa0|\s/g, ' '); 252 | }; 253 | 254 | 255 | /** 256 | * Normalizes spaces in a string, replacing all consecutive spaces and tabs 257 | * with a single space. Replaces non-breaking space with a space. 258 | * @param {string} str The string in which to normalize spaces. 259 | * @return {string} A copy of {@code str} with all consecutive spaces and tabs 260 | * replaced with a single space. 261 | */ 262 | goog.string.normalizeSpaces = function(str) { 263 | return str.replace(/\xa0|[ \t]+/g, ' '); 264 | }; 265 | 266 | 267 | /** 268 | * Removes the breaking spaces from the left and right of the string and 269 | * collapses the sequences of breaking spaces in the middle into single spaces. 270 | * The original and the result strings render the same way in HTML. 271 | * @param {string} str A string in which to collapse spaces. 272 | * @return {string} Copy of the string with normalized breaking spaces. 273 | */ 274 | goog.string.collapseBreakingSpaces = function(str) { 275 | return str.replace(/[\t\r\n ]+/g, ' ').replace( 276 | /^[\t\r\n ]+|[\t\r\n ]+$/g, ''); 277 | }; 278 | 279 | 280 | /** 281 | * Trims white spaces to the left and right of a string. 282 | * @param {string} str The string to trim. 283 | * @return {string} A trimmed copy of {@code str}. 284 | */ 285 | goog.string.trim = function(str) { 286 | // Since IE doesn't include non-breaking-space (0xa0) in their \s character 287 | // class (as required by section 7.2 of the ECMAScript spec), we explicitly 288 | // include it in the regexp to enforce consistent cross-browser behavior. 289 | return str.replace(/^[\s\xa0]+|[\s\xa0]+$/g, ''); 290 | }; 291 | 292 | 293 | /** 294 | * Trims whitespaces at the left end of a string. 295 | * @param {string} str The string to left trim. 296 | * @return {string} A trimmed copy of {@code str}. 297 | */ 298 | goog.string.trimLeft = function(str) { 299 | // Since IE doesn't include non-breaking-space (0xa0) in their \s character 300 | // class (as required by section 7.2 of the ECMAScript spec), we explicitly 301 | // include it in the regexp to enforce consistent cross-browser behavior. 302 | return str.replace(/^[\s\xa0]+/, ''); 303 | }; 304 | 305 | 306 | /** 307 | * Trims whitespaces at the right end of a string. 308 | * @param {string} str The string to right trim. 309 | * @return {string} A trimmed copy of {@code str}. 310 | */ 311 | goog.string.trimRight = function(str) { 312 | // Since IE doesn't include non-breaking-space (0xa0) in their \s character 313 | // class (as required by section 7.2 of the ECMAScript spec), we explicitly 314 | // include it in the regexp to enforce consistent cross-browser behavior. 315 | return str.replace(/[\s\xa0]+$/, ''); 316 | }; 317 | 318 | 319 | /** 320 | * A string comparator that ignores case. 321 | * -1 = str1 less than str2 322 | * 0 = str1 equals str2 323 | * 1 = str1 greater than str2 324 | * 325 | * @param {string} str1 The string to compare. 326 | * @param {string} str2 The string to compare {@code str1} to. 327 | * @return {number} The comparator result, as described above. 328 | */ 329 | goog.string.caseInsensitiveCompare = function(str1, str2) { 330 | var test1 = String(str1).toLowerCase(); 331 | var test2 = String(str2).toLowerCase(); 332 | 333 | if (test1 < test2) { 334 | return -1; 335 | } else if (test1 == test2) { 336 | return 0; 337 | } else { 338 | return 1; 339 | } 340 | }; 341 | 342 | 343 | /** 344 | * Regular expression used for splitting a string into substrings of fractional 345 | * numbers, integers, and non-numeric characters. 346 | * @type {RegExp} 347 | * @private 348 | */ 349 | goog.string.numerateCompareRegExp_ = /(\.\d+)|(\d+)|(\D+)/g; 350 | 351 | 352 | /** 353 | * String comparison function that handles numbers in a way humans might expect. 354 | * Using this function, the string "File 2.jpg" sorts before "File 10.jpg". The 355 | * comparison is mostly case-insensitive, though strings that are identical 356 | * except for case are sorted with the upper-case strings before lower-case. 357 | * 358 | * This comparison function is significantly slower (about 500x) than either 359 | * the default or the case-insensitive compare. It should not be used in 360 | * time-critical code, but should be fast enough to sort several hundred short 361 | * strings (like filenames) with a reasonable delay. 362 | * 363 | * @param {string} str1 The string to compare in a numerically sensitive way. 364 | * @param {string} str2 The string to compare {@code str1} to. 365 | * @return {number} less than 0 if str1 < str2, 0 if str1 == str2, greater than 366 | * 0 if str1 > str2. 367 | */ 368 | goog.string.numerateCompare = function(str1, str2) { 369 | if (str1 == str2) { 370 | return 0; 371 | } 372 | if (!str1) { 373 | return -1; 374 | } 375 | if (!str2) { 376 | return 1; 377 | } 378 | 379 | // Using match to split the entire string ahead of time turns out to be faster 380 | // for most inputs than using RegExp.exec or iterating over each character. 381 | var tokens1 = str1.toLowerCase().match(goog.string.numerateCompareRegExp_); 382 | var tokens2 = str2.toLowerCase().match(goog.string.numerateCompareRegExp_); 383 | 384 | var count = Math.min(tokens1.length, tokens2.length); 385 | 386 | for (var i = 0; i < count; i++) { 387 | var a = tokens1[i]; 388 | var b = tokens2[i]; 389 | 390 | // Compare pairs of tokens, returning if one token sorts before the other. 391 | if (a != b) { 392 | 393 | // Only if both tokens are integers is a special comparison required. 394 | // Decimal numbers are sorted as strings (e.g., '.09' < '.1'). 395 | var num1 = parseInt(a, 10); 396 | if (!isNaN(num1)) { 397 | var num2 = parseInt(b, 10); 398 | if (!isNaN(num2) && num1 - num2) { 399 | return num1 - num2; 400 | } 401 | } 402 | return a < b ? -1 : 1; 403 | } 404 | } 405 | 406 | // If one string is a substring of the other, the shorter string sorts first. 407 | if (tokens1.length != tokens2.length) { 408 | return tokens1.length - tokens2.length; 409 | } 410 | 411 | // The two strings must be equivalent except for case (perfect equality is 412 | // tested at the head of the function.) Revert to default ASCII-betical string 413 | // comparison to stablize the sort. 414 | return str1 < str2 ? -1 : 1; 415 | }; 416 | 417 | 418 | /** 419 | * URL-encodes a string 420 | * @param {*} str The string to url-encode. 421 | * @return {string} An encoded copy of {@code str} that is safe for urls. 422 | * Note that '#', ':', and other characters used to delimit portions 423 | * of URLs *will* be encoded. 424 | */ 425 | goog.string.urlEncode = function(str) { 426 | return encodeURIComponent(String(str)); 427 | }; 428 | 429 | 430 | /** 431 | * URL-decodes the string. We need to specially handle '+'s because 432 | * the javascript library doesn't convert them to spaces. 433 | * @param {string} str The string to url decode. 434 | * @return {string} The decoded {@code str}. 435 | */ 436 | goog.string.urlDecode = function(str) { 437 | return decodeURIComponent(str.replace(/\+/g, ' ')); 438 | }; 439 | 440 | 441 | /** 442 | * Converts \n to
s or
s. 443 | * @param {string} str The string in which to convert newlines. 444 | * @param {boolean=} opt_xml Whether to use XML compatible tags. 445 | * @return {string} A copy of {@code str} with converted newlines. 446 | */ 447 | goog.string.newLineToBr = function(str, opt_xml) { 448 | return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '
' : '
'); 449 | }; 450 | 451 | 452 | /** 453 | * Escape double quote '"' characters in addition to '&', '<', and '>' so that a 454 | * string can be included in an HTML tag attribute value within double quotes. 455 | * 456 | * It should be noted that > doesn't need to be escaped for the HTML or XML to 457 | * be valid, but it has been decided to escape it for consistency with other 458 | * implementations. 459 | * 460 | * NOTE(user): 461 | * HtmlEscape is often called during the generation of large blocks of HTML. 462 | * Using statics for the regular expressions and strings is an optimization 463 | * that can more than half the amount of time IE spends in this function for 464 | * large apps, since strings and regexes both contribute to GC allocations. 465 | * 466 | * Testing for the presence of a character before escaping increases the number 467 | * of function calls, but actually provides a speed increase for the average 468 | * case -- since the average case often doesn't require the escaping of all 4 469 | * characters and indexOf() is much cheaper than replace(). 470 | * The worst case does suffer slightly from the additional calls, therefore the 471 | * opt_isLikelyToContainHtmlChars option has been included for situations 472 | * where all 4 HTML entities are very likely to be present and need escaping. 473 | * 474 | * Some benchmarks (times tended to fluctuate +-0.05ms): 475 | * FireFox IE6 476 | * (no chars / average (mix of cases) / all 4 chars) 477 | * no checks 0.13 / 0.22 / 0.22 0.23 / 0.53 / 0.80 478 | * indexOf 0.08 / 0.17 / 0.26 0.22 / 0.54 / 0.84 479 | * indexOf + re test 0.07 / 0.17 / 0.28 0.19 / 0.50 / 0.85 480 | * 481 | * An additional advantage of checking if replace actually needs to be called 482 | * is a reduction in the number of object allocations, so as the size of the 483 | * application grows the difference between the various methods would increase. 484 | * 485 | * @param {string} str string to be escaped. 486 | * @param {boolean=} opt_isLikelyToContainHtmlChars Don't perform a check to see 487 | * if the character needs replacing - use this option if you expect each of 488 | * the characters to appear often. Leave false if you expect few html 489 | * characters to occur in your strings, such as if you are escaping HTML. 490 | * @return {string} An escaped copy of {@code str}. 491 | */ 492 | goog.string.htmlEscape = function(str, opt_isLikelyToContainHtmlChars) { 493 | 494 | if (opt_isLikelyToContainHtmlChars) { 495 | return str.replace(goog.string.amperRe_, '&') 496 | .replace(goog.string.ltRe_, '<') 497 | .replace(goog.string.gtRe_, '>') 498 | .replace(goog.string.quotRe_, '"') 499 | .replace(goog.string.singleQuoteRe_, '''); 500 | 501 | } else { 502 | // quick test helps in the case when there are no chars to replace, in 503 | // worst case this makes barely a difference to the time taken 504 | if (!goog.string.allRe_.test(str)) return str; 505 | 506 | // str.indexOf is faster than regex.test in this case 507 | if (str.indexOf('&') != -1) { 508 | str = str.replace(goog.string.amperRe_, '&'); 509 | } 510 | if (str.indexOf('<') != -1) { 511 | str = str.replace(goog.string.ltRe_, '<'); 512 | } 513 | if (str.indexOf('>') != -1) { 514 | str = str.replace(goog.string.gtRe_, '>'); 515 | } 516 | if (str.indexOf('"') != -1) { 517 | str = str.replace(goog.string.quotRe_, '"'); 518 | } 519 | if (str.indexOf('\'') != -1) { 520 | str = str.replace(goog.string.singleQuoteRe_, '''); 521 | } 522 | return str; 523 | } 524 | }; 525 | 526 | 527 | /** 528 | * Regular expression that matches an ampersand, for use in escaping. 529 | * @type {RegExp} 530 | * @private 531 | */ 532 | goog.string.amperRe_ = /&/g; 533 | 534 | 535 | /** 536 | * Regular expression that matches a less than sign, for use in escaping. 537 | * @type {RegExp} 538 | * @private 539 | */ 540 | goog.string.ltRe_ = //g; 549 | 550 | 551 | /** 552 | * Regular expression that matches a double quote, for use in escaping. 553 | * @type {RegExp} 554 | * @private 555 | */ 556 | goog.string.quotRe_ = /"/g; 557 | 558 | 559 | /** 560 | * Regular expression that matches a single quote, for use in escaping. 561 | * @type {RegExp} 562 | * @private 563 | */ 564 | goog.string.singleQuoteRe_ = /'/g; 565 | 566 | 567 | /** 568 | * Regular expression that matches any character that needs to be escaped. 569 | * @type {RegExp} 570 | * @private 571 | */ 572 | goog.string.allRe_ = /[&<>"']/; 573 | 574 | 575 | /** 576 | * Unescapes an HTML string. 577 | * 578 | * @param {string} str The string to unescape. 579 | * @return {string} An unescaped copy of {@code str}. 580 | */ 581 | goog.string.unescapeEntities = function(str) { 582 | if (goog.string.contains(str, '&')) { 583 | // We are careful not to use a DOM if we do not have one. We use the [] 584 | // notation so that the JSCompiler will not complain about these objects and 585 | // fields in the case where we have no DOM. 586 | if ('document' in goog.global) { 587 | return goog.string.unescapeEntitiesUsingDom_(str); 588 | } else { 589 | // Fall back on pure XML entities 590 | return goog.string.unescapePureXmlEntities_(str); 591 | } 592 | } 593 | return str; 594 | }; 595 | 596 | 597 | /** 598 | * Unescapes a HTML string using the provided document. 599 | * 600 | * @param {string} str The string to unescape. 601 | * @param {!Document} document A document to use in escaping the string. 602 | * @return {string} An unescaped copy of {@code str}. 603 | */ 604 | goog.string.unescapeEntitiesWithDocument = function(str, document) { 605 | if (goog.string.contains(str, '&')) { 606 | return goog.string.unescapeEntitiesUsingDom_(str, document); 607 | } 608 | return str; 609 | }; 610 | 611 | 612 | /** 613 | * Unescapes an HTML string using a DOM to resolve non-XML, non-numeric 614 | * entities. This function is XSS-safe and whitespace-preserving. 615 | * @private 616 | * @param {string} str The string to unescape. 617 | * @param {Document=} opt_document An optional document to use for creating 618 | * elements. If this is not specified then the default window.document 619 | * will be used. 620 | * @return {string} The unescaped {@code str} string. 621 | */ 622 | goog.string.unescapeEntitiesUsingDom_ = function(str, opt_document) { 623 | var seen = {'&': '&', '<': '<', '>': '>', '"': '"'}; 624 | var div; 625 | if (opt_document) { 626 | div = opt_document.createElement('div'); 627 | } else { 628 | div = document.createElement('div'); 629 | } 630 | // Match as many valid entity characters as possible. If the actual entity 631 | // happens to be shorter, it will still work as innerHTML will return the 632 | // trailing characters unchanged. Since the entity characters do not include 633 | // open angle bracket, there is no chance of XSS from the innerHTML use. 634 | // Since no whitespace is passed to innerHTML, whitespace is preserved. 635 | return str.replace(goog.string.HTML_ENTITY_PATTERN_, function(s, entity) { 636 | // Check for cached entity. 637 | var value = seen[s]; 638 | if (value) { 639 | return value; 640 | } 641 | // Check for numeric entity. 642 | if (entity.charAt(0) == '#') { 643 | // Prefix with 0 so that hex entities (e.g. ) parse as hex numbers. 644 | var n = Number('0' + entity.substr(1)); 645 | if (!isNaN(n)) { 646 | value = String.fromCharCode(n); 647 | } 648 | } 649 | // Fall back to innerHTML otherwise. 650 | if (!value) { 651 | // Append a non-entity character to avoid a bug in Webkit that parses 652 | // an invalid entity at the end of innerHTML text as the empty string. 653 | div.innerHTML = s + ' '; 654 | // Then remove the trailing character from the result. 655 | value = div.firstChild.nodeValue.slice(0, -1); 656 | } 657 | // Cache and return. 658 | return seen[s] = value; 659 | }); 660 | }; 661 | 662 | 663 | /** 664 | * Unescapes XML entities. 665 | * @private 666 | * @param {string} str The string to unescape. 667 | * @return {string} An unescaped copy of {@code str}. 668 | */ 669 | goog.string.unescapePureXmlEntities_ = function(str) { 670 | return str.replace(/&([^;]+);/g, function(s, entity) { 671 | switch (entity) { 672 | case 'amp': 673 | return '&'; 674 | case 'lt': 675 | return '<'; 676 | case 'gt': 677 | return '>'; 678 | case 'quot': 679 | return '"'; 680 | default: 681 | if (entity.charAt(0) == '#') { 682 | // Prefix with 0 so that hex entities (e.g. ) parse as hex. 683 | var n = Number('0' + entity.substr(1)); 684 | if (!isNaN(n)) { 685 | return String.fromCharCode(n); 686 | } 687 | } 688 | // For invalid entities we just return the entity 689 | return s; 690 | } 691 | }); 692 | }; 693 | 694 | 695 | /** 696 | * Regular expression that matches an HTML entity. 697 | * See also HTML5: Tokenization / Tokenizing character references. 698 | * @private 699 | * @type {!RegExp} 700 | */ 701 | goog.string.HTML_ENTITY_PATTERN_ = /&([^;\s<&]+);?/g; 702 | 703 | 704 | /** 705 | * Do escaping of whitespace to preserve spatial formatting. We use character 706 | * entity #160 to make it safer for xml. 707 | * @param {string} str The string in which to escape whitespace. 708 | * @param {boolean=} opt_xml Whether to use XML compatible tags. 709 | * @return {string} An escaped copy of {@code str}. 710 | */ 711 | goog.string.whitespaceEscape = function(str, opt_xml) { 712 | return goog.string.newLineToBr(str.replace(/ /g, '  '), opt_xml); 713 | }; 714 | 715 | 716 | /** 717 | * Strip quote characters around a string. The second argument is a string of 718 | * characters to treat as quotes. This can be a single character or a string of 719 | * multiple character and in that case each of those are treated as possible 720 | * quote characters. For example: 721 | * 722 | *
 723 |  * goog.string.stripQuotes('"abc"', '"`') --> 'abc'
 724 |  * goog.string.stripQuotes('`abc`', '"`') --> 'abc'
 725 |  * 
726 | * 727 | * @param {string} str The string to strip. 728 | * @param {string} quoteChars The quote characters to strip. 729 | * @return {string} A copy of {@code str} without the quotes. 730 | */ 731 | goog.string.stripQuotes = function(str, quoteChars) { 732 | var length = quoteChars.length; 733 | for (var i = 0; i < length; i++) { 734 | var quoteChar = length == 1 ? quoteChars : quoteChars.charAt(i); 735 | if (str.charAt(0) == quoteChar && str.charAt(str.length - 1) == quoteChar) { 736 | return str.substring(1, str.length - 1); 737 | } 738 | } 739 | return str; 740 | }; 741 | 742 | 743 | /** 744 | * Truncates a string to a certain length and adds '...' if necessary. The 745 | * length also accounts for the ellipsis, so a maximum length of 10 and a string 746 | * 'Hello World!' produces 'Hello W...'. 747 | * @param {string} str The string to truncate. 748 | * @param {number} chars Max number of characters. 749 | * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped 750 | * characters from being cut off in the middle. 751 | * @return {string} The truncated {@code str} string. 752 | */ 753 | goog.string.truncate = function(str, chars, opt_protectEscapedCharacters) { 754 | if (opt_protectEscapedCharacters) { 755 | str = goog.string.unescapeEntities(str); 756 | } 757 | 758 | if (str.length > chars) { 759 | str = str.substring(0, chars - 3) + '...'; 760 | } 761 | 762 | if (opt_protectEscapedCharacters) { 763 | str = goog.string.htmlEscape(str); 764 | } 765 | 766 | return str; 767 | }; 768 | 769 | 770 | /** 771 | * Truncate a string in the middle, adding "..." if necessary, 772 | * and favoring the beginning of the string. 773 | * @param {string} str The string to truncate the middle of. 774 | * @param {number} chars Max number of characters. 775 | * @param {boolean=} opt_protectEscapedCharacters Whether to protect escaped 776 | * characters from being cutoff in the middle. 777 | * @param {number=} opt_trailingChars Optional number of trailing characters to 778 | * leave at the end of the string, instead of truncating as close to the 779 | * middle as possible. 780 | * @return {string} A truncated copy of {@code str}. 781 | */ 782 | goog.string.truncateMiddle = function(str, chars, 783 | opt_protectEscapedCharacters, opt_trailingChars) { 784 | if (opt_protectEscapedCharacters) { 785 | str = goog.string.unescapeEntities(str); 786 | } 787 | 788 | if (opt_trailingChars && str.length > chars) { 789 | if (opt_trailingChars > chars) { 790 | opt_trailingChars = chars; 791 | } 792 | var endPoint = str.length - opt_trailingChars; 793 | var startPoint = chars - opt_trailingChars; 794 | str = str.substring(0, startPoint) + '...' + str.substring(endPoint); 795 | } else if (str.length > chars) { 796 | // Favor the beginning of the string: 797 | var half = Math.floor(chars / 2); 798 | var endPos = str.length - half; 799 | half += chars % 2; 800 | str = str.substring(0, half) + '...' + str.substring(endPos); 801 | } 802 | 803 | if (opt_protectEscapedCharacters) { 804 | str = goog.string.htmlEscape(str); 805 | } 806 | 807 | return str; 808 | }; 809 | 810 | 811 | /** 812 | * Special chars that need to be escaped for goog.string.quote. 813 | * @private 814 | * @type {Object} 815 | */ 816 | goog.string.specialEscapeChars_ = { 817 | '\0': '\\0', 818 | '\b': '\\b', 819 | '\f': '\\f', 820 | '\n': '\\n', 821 | '\r': '\\r', 822 | '\t': '\\t', 823 | '\x0B': '\\x0B', // '\v' is not supported in JScript 824 | '"': '\\"', 825 | '\\': '\\\\' 826 | }; 827 | 828 | 829 | /** 830 | * Character mappings used internally for goog.string.escapeChar. 831 | * @private 832 | * @type {Object} 833 | */ 834 | goog.string.jsEscapeCache_ = { 835 | '\'': '\\\'' 836 | }; 837 | 838 | 839 | /** 840 | * Encloses a string in double quotes and escapes characters so that the 841 | * string is a valid JS string. 842 | * @param {string} s The string to quote. 843 | * @return {string} A copy of {@code s} surrounded by double quotes. 844 | */ 845 | goog.string.quote = function(s) { 846 | s = String(s); 847 | if (s.quote) { 848 | return s.quote(); 849 | } else { 850 | var sb = ['"']; 851 | for (var i = 0; i < s.length; i++) { 852 | var ch = s.charAt(i); 853 | var cc = ch.charCodeAt(0); 854 | sb[i + 1] = goog.string.specialEscapeChars_[ch] || 855 | ((cc > 31 && cc < 127) ? ch : goog.string.escapeChar(ch)); 856 | } 857 | sb.push('"'); 858 | return sb.join(''); 859 | } 860 | }; 861 | 862 | 863 | /** 864 | * Takes a string and returns the escaped string for that character. 865 | * @param {string} str The string to escape. 866 | * @return {string} An escaped string representing {@code str}. 867 | */ 868 | goog.string.escapeString = function(str) { 869 | var sb = []; 870 | for (var i = 0; i < str.length; i++) { 871 | sb[i] = goog.string.escapeChar(str.charAt(i)); 872 | } 873 | return sb.join(''); 874 | }; 875 | 876 | 877 | /** 878 | * Takes a character and returns the escaped string for that character. For 879 | * example escapeChar(String.fromCharCode(15)) -> "\\x0E". 880 | * @param {string} c The character to escape. 881 | * @return {string} An escaped string representing {@code c}. 882 | */ 883 | goog.string.escapeChar = function(c) { 884 | if (c in goog.string.jsEscapeCache_) { 885 | return goog.string.jsEscapeCache_[c]; 886 | } 887 | 888 | if (c in goog.string.specialEscapeChars_) { 889 | return goog.string.jsEscapeCache_[c] = goog.string.specialEscapeChars_[c]; 890 | } 891 | 892 | var rv = c; 893 | var cc = c.charCodeAt(0); 894 | if (cc > 31 && cc < 127) { 895 | rv = c; 896 | } else { 897 | // tab is 9 but handled above 898 | if (cc < 256) { 899 | rv = '\\x'; 900 | if (cc < 16 || cc > 256) { 901 | rv += '0'; 902 | } 903 | } else { 904 | rv = '\\u'; 905 | if (cc < 4096) { // \u1000 906 | rv += '0'; 907 | } 908 | } 909 | rv += cc.toString(16).toUpperCase(); 910 | } 911 | 912 | return goog.string.jsEscapeCache_[c] = rv; 913 | }; 914 | 915 | 916 | /** 917 | * Takes a string and creates a map (Object) in which the keys are the 918 | * characters in the string. The value for the key is set to true. You can 919 | * then use goog.object.map or goog.array.map to change the values. 920 | * @param {string} s The string to build the map from. 921 | * @return {Object} The map of characters used. 922 | */ 923 | // TODO(arv): It seems like we should have a generic goog.array.toMap. But do 924 | // we want a dependency on goog.array in goog.string? 925 | goog.string.toMap = function(s) { 926 | var rv = {}; 927 | for (var i = 0; i < s.length; i++) { 928 | rv[s.charAt(i)] = true; 929 | } 930 | return rv; 931 | }; 932 | 933 | 934 | /** 935 | * Checks whether a string contains a given substring. 936 | * @param {string} s The string to test. 937 | * @param {string} ss The substring to test for. 938 | * @return {boolean} True if {@code s} contains {@code ss}. 939 | */ 940 | goog.string.contains = function(s, ss) { 941 | return s.indexOf(ss) != -1; 942 | }; 943 | 944 | 945 | /** 946 | * Returns the non-overlapping occurrences of ss in s. 947 | * If either s or ss evalutes to false, then returns zero. 948 | * @param {string} s The string to look in. 949 | * @param {string} ss The string to look for. 950 | * @return {number} Number of occurrences of ss in s. 951 | */ 952 | goog.string.countOf = function(s, ss) { 953 | return s && ss ? s.split(ss).length - 1 : 0; 954 | }; 955 | 956 | 957 | /** 958 | * Removes a substring of a specified length at a specific 959 | * index in a string. 960 | * @param {string} s The base string from which to remove. 961 | * @param {number} index The index at which to remove the substring. 962 | * @param {number} stringLength The length of the substring to remove. 963 | * @return {string} A copy of {@code s} with the substring removed or the full 964 | * string if nothing is removed or the input is invalid. 965 | */ 966 | goog.string.removeAt = function(s, index, stringLength) { 967 | var resultStr = s; 968 | // If the index is greater or equal to 0 then remove substring 969 | if (index >= 0 && index < s.length && stringLength > 0) { 970 | resultStr = s.substr(0, index) + 971 | s.substr(index + stringLength, s.length - index - stringLength); 972 | } 973 | return resultStr; 974 | }; 975 | 976 | 977 | /** 978 | * Removes the first occurrence of a substring from a string. 979 | * @param {string} s The base string from which to remove. 980 | * @param {string} ss The string to remove. 981 | * @return {string} A copy of {@code s} with {@code ss} removed or the full 982 | * string if nothing is removed. 983 | */ 984 | goog.string.remove = function(s, ss) { 985 | var re = new RegExp(goog.string.regExpEscape(ss), ''); 986 | return s.replace(re, ''); 987 | }; 988 | 989 | 990 | /** 991 | * Removes all occurrences of a substring from a string. 992 | * @param {string} s The base string from which to remove. 993 | * @param {string} ss The string to remove. 994 | * @return {string} A copy of {@code s} with {@code ss} removed or the full 995 | * string if nothing is removed. 996 | */ 997 | goog.string.removeAll = function(s, ss) { 998 | var re = new RegExp(goog.string.regExpEscape(ss), 'g'); 999 | return s.replace(re, ''); 1000 | }; 1001 | 1002 | 1003 | /** 1004 | * Escapes characters in the string that are not safe to use in a RegExp. 1005 | * @param {*} s The string to escape. If not a string, it will be casted 1006 | * to one. 1007 | * @return {string} A RegExp safe, escaped copy of {@code s}. 1008 | */ 1009 | goog.string.regExpEscape = function(s) { 1010 | return String(s).replace(/([-()\[\]{}+?*.$\^|,:#padNumber(1.25, 2, 3) -> '01.250' 1031 | * padNumber(1.25, 2) -> '01.25' 1032 | * padNumber(1.25, 2, 1) -> '01.3' 1033 | * padNumber(1.25, 0) -> '1.25' 1034 | * 1035 | * @param {number} num The number to pad. 1036 | * @param {number} length The desired length. 1037 | * @param {number=} opt_precision The desired precision. 1038 | * @return {string} {@code num} as a string with the given options. 1039 | */ 1040 | goog.string.padNumber = function(num, length, opt_precision) { 1041 | var s = goog.isDef(opt_precision) ? num.toFixed(opt_precision) : String(num); 1042 | var index = s.indexOf('.'); 1043 | if (index == -1) { 1044 | index = s.length; 1045 | } 1046 | return goog.string.repeat('0', Math.max(0, length - index)) + s; 1047 | }; 1048 | 1049 | 1050 | /** 1051 | * Returns a string representation of the given object, with 1052 | * null and undefined being returned as the empty string. 1053 | * 1054 | * @param {*} obj The object to convert. 1055 | * @return {string} A string representation of the {@code obj}. 1056 | */ 1057 | goog.string.makeSafe = function(obj) { 1058 | return obj == null ? '' : String(obj); 1059 | }; 1060 | 1061 | 1062 | /** 1063 | * Concatenates string expressions. This is useful 1064 | * since some browsers are very inefficient when it comes to using plus to 1065 | * concat strings. Be careful when using null and undefined here since 1066 | * these will not be included in the result. If you need to represent these 1067 | * be sure to cast the argument to a String first. 1068 | * For example: 1069 | *
buildString('a', 'b', 'c', 'd') -> 'abcd'
1070 |  * buildString(null, undefined) -> ''
1071 |  * 
1072 | * @param {...*} var_args A list of strings to concatenate. If not a string, 1073 | * it will be casted to one. 1074 | * @return {string} The concatenation of {@code var_args}. 1075 | */ 1076 | goog.string.buildString = function(var_args) { 1077 | return Array.prototype.join.call(arguments, ''); 1078 | }; 1079 | 1080 | 1081 | /** 1082 | * Returns a string with at least 64-bits of randomness. 1083 | * 1084 | * Doesn't trust Javascript's random function entirely. Uses a combination of 1085 | * random and current timestamp, and then encodes the string in base-36 to 1086 | * make it shorter. 1087 | * 1088 | * @return {string} A random string, e.g. sn1s7vb4gcic. 1089 | */ 1090 | goog.string.getRandomString = function() { 1091 | var x = 2147483648; 1092 | return Math.floor(Math.random() * x).toString(36) + 1093 | Math.abs(Math.floor(Math.random() * x) ^ goog.now()).toString(36); 1094 | }; 1095 | 1096 | 1097 | /** 1098 | * Compares two version numbers. 1099 | * 1100 | * @param {string|number} version1 Version of first item. 1101 | * @param {string|number} version2 Version of second item. 1102 | * 1103 | * @return {number} 1 if {@code version1} is higher. 1104 | * 0 if arguments are equal. 1105 | * -1 if {@code version2} is higher. 1106 | */ 1107 | goog.string.compareVersions = function(version1, version2) { 1108 | var order = 0; 1109 | // Trim leading and trailing whitespace and split the versions into 1110 | // subversions. 1111 | var v1Subs = goog.string.trim(String(version1)).split('.'); 1112 | var v2Subs = goog.string.trim(String(version2)).split('.'); 1113 | var subCount = Math.max(v1Subs.length, v2Subs.length); 1114 | 1115 | // Iterate over the subversions, as long as they appear to be equivalent. 1116 | for (var subIdx = 0; order == 0 && subIdx < subCount; subIdx++) { 1117 | var v1Sub = v1Subs[subIdx] || ''; 1118 | var v2Sub = v2Subs[subIdx] || ''; 1119 | 1120 | // Split the subversions into pairs of numbers and qualifiers (like 'b'). 1121 | // Two different RegExp objects are needed because they are both using 1122 | // the 'g' flag. 1123 | var v1CompParser = new RegExp('(\\d*)(\\D*)', 'g'); 1124 | var v2CompParser = new RegExp('(\\d*)(\\D*)', 'g'); 1125 | do { 1126 | var v1Comp = v1CompParser.exec(v1Sub) || ['', '', '']; 1127 | var v2Comp = v2CompParser.exec(v2Sub) || ['', '', '']; 1128 | // Break if there are no more matches. 1129 | if (v1Comp[0].length == 0 && v2Comp[0].length == 0) { 1130 | break; 1131 | } 1132 | 1133 | // Parse the numeric part of the subversion. A missing number is 1134 | // equivalent to 0. 1135 | var v1CompNum = v1Comp[1].length == 0 ? 0 : parseInt(v1Comp[1], 10); 1136 | var v2CompNum = v2Comp[1].length == 0 ? 0 : parseInt(v2Comp[1], 10); 1137 | 1138 | // Compare the subversion components. The number has the highest 1139 | // precedence. Next, if the numbers are equal, a subversion without any 1140 | // qualifier is always higher than a subversion with any qualifier. Next, 1141 | // the qualifiers are compared as strings. 1142 | order = goog.string.compareElements_(v1CompNum, v2CompNum) || 1143 | goog.string.compareElements_(v1Comp[2].length == 0, 1144 | v2Comp[2].length == 0) || 1145 | goog.string.compareElements_(v1Comp[2], v2Comp[2]); 1146 | // Stop as soon as an inequality is discovered. 1147 | } while (order == 0); 1148 | } 1149 | 1150 | return order; 1151 | }; 1152 | 1153 | 1154 | /** 1155 | * Compares elements of a version number. 1156 | * 1157 | * @param {string|number|boolean} left An element from a version number. 1158 | * @param {string|number|boolean} right An element from a version number. 1159 | * 1160 | * @return {number} 1 if {@code left} is higher. 1161 | * 0 if arguments are equal. 1162 | * -1 if {@code right} is higher. 1163 | * @private 1164 | */ 1165 | goog.string.compareElements_ = function(left, right) { 1166 | if (left < right) { 1167 | return -1; 1168 | } else if (left > right) { 1169 | return 1; 1170 | } 1171 | return 0; 1172 | }; 1173 | 1174 | 1175 | /** 1176 | * Maximum value of #goog.string.hashCode, exclusive. 2^32. 1177 | * @type {number} 1178 | * @private 1179 | */ 1180 | goog.string.HASHCODE_MAX_ = 0x100000000; 1181 | 1182 | 1183 | /** 1184 | * String hash function similar to java.lang.String.hashCode(). 1185 | * The hash code for a string is computed as 1186 | * s[0] * 31 ^ (n - 1) + s[1] * 31 ^ (n - 2) + ... + s[n - 1], 1187 | * where s[i] is the ith character of the string and n is the length of 1188 | * the string. We mod the result to make it between 0 (inclusive) and 2^32 1189 | * (exclusive). 1190 | * @param {string} str A string. 1191 | * @return {number} Hash value for {@code str}, between 0 (inclusive) and 2^32 1192 | * (exclusive). The empty string returns 0. 1193 | */ 1194 | goog.string.hashCode = function(str) { 1195 | var result = 0; 1196 | for (var i = 0; i < str.length; ++i) { 1197 | result = 31 * result + str.charCodeAt(i); 1198 | // Normalize to 4 byte range, 0 ... 2^32. 1199 | result %= goog.string.HASHCODE_MAX_; 1200 | } 1201 | return result; 1202 | }; 1203 | 1204 | 1205 | /** 1206 | * The most recent unique ID. |0 is equivalent to Math.floor in this case. 1207 | * @type {number} 1208 | * @private 1209 | */ 1210 | goog.string.uniqueStringCounter_ = Math.random() * 0x80000000 | 0; 1211 | 1212 | 1213 | /** 1214 | * Generates and returns a string which is unique in the current document. 1215 | * This is useful, for example, to create unique IDs for DOM elements. 1216 | * @return {string} A unique id. 1217 | */ 1218 | goog.string.createUniqueString = function() { 1219 | return 'goog_' + goog.string.uniqueStringCounter_++; 1220 | }; 1221 | 1222 | 1223 | /** 1224 | * Converts the supplied string to a number, which may be Infinity or NaN. 1225 | * This function strips whitespace: (toNumber(' 123') === 123) 1226 | * This function accepts scientific notation: (toNumber('1e1') === 10) 1227 | * 1228 | * This is better than Javascript's built-in conversions because, sadly: 1229 | * (Number(' ') === 0) and (parseFloat('123a') === 123) 1230 | * 1231 | * @param {string} str The string to convert. 1232 | * @return {number} The number the supplied string represents, or NaN. 1233 | */ 1234 | goog.string.toNumber = function(str) { 1235 | var num = Number(str); 1236 | if (num == 0 && goog.string.isEmpty(str)) { 1237 | return NaN; 1238 | } 1239 | return num; 1240 | }; 1241 | 1242 | 1243 | /** 1244 | * Returns whether the given string is lower camel case (e.g. "isFooBar"). 1245 | * 1246 | * Note that this assumes the string is entirely letters. 1247 | * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms 1248 | * 1249 | * @param {string} str String to test. 1250 | * @return {boolean} Whether the string is lower camel case. 1251 | */ 1252 | goog.string.isLowerCamelCase = function(str) { 1253 | return /^[a-z]+([A-Z][a-z]*)*$/.test(str); 1254 | }; 1255 | 1256 | 1257 | /** 1258 | * Returns whether the given string is upper camel case (e.g. "FooBarBaz"). 1259 | * 1260 | * Note that this assumes the string is entirely letters. 1261 | * @see http://en.wikipedia.org/wiki/CamelCase#Variations_and_synonyms 1262 | * 1263 | * @param {string} str String to test. 1264 | * @return {boolean} Whether the string is upper camel case. 1265 | */ 1266 | goog.string.isUpperCamelCase = function(str) { 1267 | return /^([A-Z][a-z]*)+$/.test(str); 1268 | }; 1269 | 1270 | 1271 | /** 1272 | * Converts a string from selector-case to camelCase (e.g. from 1273 | * "multi-part-string" to "multiPartString"), useful for converting 1274 | * CSS selectors and HTML dataset keys to their equivalent JS properties. 1275 | * @param {string} str The string in selector-case form. 1276 | * @return {string} The string in camelCase form. 1277 | */ 1278 | goog.string.toCamelCase = function(str) { 1279 | return String(str).replace(/\-([a-z])/g, function(all, match) { 1280 | return match.toUpperCase(); 1281 | }); 1282 | }; 1283 | 1284 | 1285 | /** 1286 | * Converts a string from camelCase to selector-case (e.g. from 1287 | * "multiPartString" to "multi-part-string"), useful for converting JS 1288 | * style and dataset properties to equivalent CSS selectors and HTML keys. 1289 | * @param {string} str The string in camelCase form. 1290 | * @return {string} The string in selector-case form. 1291 | */ 1292 | goog.string.toSelectorCase = function(str) { 1293 | return String(str).replace(/([A-Z])/g, '-$1').toLowerCase(); 1294 | }; 1295 | 1296 | 1297 | /** 1298 | * Converts a string into TitleCase. First character of the string is always 1299 | * capitalized in addition to the first letter of every subsequent word. 1300 | * Words are delimited by one or more whitespaces by default. Custom delimiters 1301 | * can optionally be specified to replace the default, which doesn't preserve 1302 | * whitespace delimiters and instead must be explicitly included if needed. 1303 | * 1304 | * Default delimiter => " ": 1305 | * goog.string.toTitleCase('oneTwoThree') => 'OneTwoThree' 1306 | * goog.string.toTitleCase('one two three') => 'One Two Three' 1307 | * goog.string.toTitleCase(' one two ') => ' One Two ' 1308 | * goog.string.toTitleCase('one_two_three') => 'One_two_three' 1309 | * goog.string.toTitleCase('one-two-three') => 'One-two-three' 1310 | * 1311 | * Custom delimiter => "_-.": 1312 | * goog.string.toTitleCase('oneTwoThree', '_-.') => 'OneTwoThree' 1313 | * goog.string.toTitleCase('one two three', '_-.') => 'One two three' 1314 | * goog.string.toTitleCase(' one two ', '_-.') => ' one two ' 1315 | * goog.string.toTitleCase('one_two_three', '_-.') => 'One_Two_Three' 1316 | * goog.string.toTitleCase('one-two-three', '_-.') => 'One-Two-Three' 1317 | * goog.string.toTitleCase('one...two...three', '_-.') => 'One...Two...Three' 1318 | * goog.string.toTitleCase('one. two. three', '_-.') => 'One. two. three' 1319 | * goog.string.toTitleCase('one-two.three', '_-.') => 'One-Two.Three' 1320 | * 1321 | * @param {string} str String value in camelCase form. 1322 | * @param {string=} opt_delimiters Custom delimiter character set used to 1323 | * distinguish words in the string value. Each character represents a 1324 | * single delimiter. When provided, default whitespace delimiter is 1325 | * overridden and must be explicitly included if needed. 1326 | * @return {string} String value in TitleCase form. 1327 | */ 1328 | goog.string.toTitleCase = function(str, opt_delimiters) { 1329 | var delimiters = goog.isString(opt_delimiters) ? 1330 | goog.string.regExpEscape(opt_delimiters) : '\\s'; 1331 | 1332 | // For IE8, we need to prevent using an empty character set. Otherwise, 1333 | // incorrect matching will occur. 1334 | delimiters = delimiters ? '|[' + delimiters + ']+' : ''; 1335 | 1336 | var regexp = new RegExp('(^' + delimiters + ')([a-z])', 'g'); 1337 | return str.replace(regexp, function(all, p1, p2) { 1338 | return p1 + p2.toUpperCase(); 1339 | }); 1340 | }; 1341 | 1342 | 1343 | /** 1344 | * Parse a string in decimal or hexidecimal ('0xFFFF') form. 1345 | * 1346 | * To parse a particular radix, please use parseInt(string, radix) directly. See 1347 | * https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/parseInt 1348 | * 1349 | * This is a wrapper for the built-in parseInt function that will only parse 1350 | * numbers as base 10 or base 16. Some JS implementations assume strings 1351 | * starting with "0" are intended to be octal. ES3 allowed but discouraged 1352 | * this behavior. ES5 forbids it. This function emulates the ES5 behavior. 1353 | * 1354 | * For more information, see Mozilla JS Reference: http://goo.gl/8RiFj 1355 | * 1356 | * @param {string|number|null|undefined} value The value to be parsed. 1357 | * @return {number} The number, parsed. If the string failed to parse, this 1358 | * will be NaN. 1359 | */ 1360 | goog.string.parseInt = function(value) { 1361 | // Force finite numbers to strings. 1362 | if (isFinite(value)) { 1363 | value = String(value); 1364 | } 1365 | 1366 | if (goog.isString(value)) { 1367 | // If the string starts with '0x' or '-0x', parse as hex. 1368 | return /^\s*-?0x/i.test(value) ? 1369 | parseInt(value, 16) : parseInt(value, 10); 1370 | } 1371 | 1372 | return NaN; 1373 | }; 1374 | 1375 | 1376 | /** 1377 | * Splits a string on a separator a limited number of times. 1378 | * 1379 | * This implementation is more similar to Python or Java, where the limit 1380 | * parameter specifies the maximum number of splits rather than truncating 1381 | * the number of results. 1382 | * 1383 | * See http://docs.python.org/2/library/stdtypes.html#str.split 1384 | * See JavaDoc: http://goo.gl/F2AsY 1385 | * See Mozilla reference: http://goo.gl/dZdZs 1386 | * 1387 | * @param {string} str String to split. 1388 | * @param {string} separator The separator. 1389 | * @param {number} limit The limit to the number of splits. The resulting array 1390 | * will have a maximum length of limit+1. Negative numbers are the same 1391 | * as zero. 1392 | * @return {!Array.} The string, split. 1393 | */ 1394 | 1395 | goog.string.splitLimit = function(str, separator, limit) { 1396 | var parts = str.split(separator); 1397 | var returnVal = []; 1398 | 1399 | // Only continue doing this while we haven't hit the limit and we have 1400 | // parts left. 1401 | while (limit > 0 && parts.length) { 1402 | returnVal.push(parts.shift()); 1403 | limit--; 1404 | } 1405 | 1406 | // If there are remaining parts, append them to the end. 1407 | if (parts.length) { 1408 | returnVal.push(parts.join(separator)); 1409 | } 1410 | 1411 | return returnVal; 1412 | }; 1413 | 1414 | -------------------------------------------------------------------------------- /goog/structs/heap.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Datastructure: Heap. 17 | * 18 | * 19 | * This file provides the implementation of a Heap datastructure. Smaller keys 20 | * rise to the top. 21 | * 22 | * The big-O notation for all operations are below: 23 | *
 24 |  *  Method          big-O
 25 |  * ----------------------------------------------------------------------------
 26 |  * - insert         O(logn)
 27 |  * - remove         O(logn)
 28 |  * - peek           O(1)
 29 |  * - contains       O(n)
 30 |  * 
31 | */ 32 | // TODO(user): Should this rely on natural ordering via some Comparable 33 | // interface? 34 | 35 | 36 | goog.provide('goog.structs.Heap'); 37 | 38 | goog.require('goog.array'); 39 | goog.require('goog.object'); 40 | goog.require('goog.structs.Node'); 41 | 42 | 43 | 44 | /** 45 | * Class for a Heap datastructure. 46 | * 47 | * @param {goog.structs.Heap|Object=} opt_heap Optional goog.structs.Heap or 48 | * Object to initialize heap with. 49 | * @constructor 50 | * @template K, V 51 | */ 52 | goog.structs.Heap = function(opt_heap) { 53 | /** 54 | * The nodes of the heap. 55 | * @private 56 | * @type {Array.} 57 | */ 58 | this.nodes_ = []; 59 | 60 | if (opt_heap) { 61 | this.insertAll(opt_heap); 62 | } 63 | }; 64 | 65 | 66 | /** 67 | * Insert the given value into the heap with the given key. 68 | * @param {K} key The key. 69 | * @param {V} value The value. 70 | */ 71 | goog.structs.Heap.prototype.insert = function(key, value) { 72 | var node = new goog.structs.Node(key, value); 73 | var nodes = this.nodes_; 74 | nodes.push(node); 75 | this.moveUp_(nodes.length - 1); 76 | }; 77 | 78 | 79 | /** 80 | * Adds multiple key-value pairs from another goog.structs.Heap or Object 81 | * @param {goog.structs.Heap|Object} heap Object containing the data to add. 82 | */ 83 | goog.structs.Heap.prototype.insertAll = function(heap) { 84 | var keys, values; 85 | if (heap instanceof goog.structs.Heap) { 86 | keys = heap.getKeys(); 87 | values = heap.getValues(); 88 | 89 | // If it is a heap and the current heap is empty, I can rely on the fact 90 | // that the keys/values are in the correct order to put in the underlying 91 | // structure. 92 | if (heap.getCount() <= 0) { 93 | var nodes = this.nodes_; 94 | for (var i = 0; i < keys.length; i++) { 95 | nodes.push(new goog.structs.Node(keys[i], values[i])); 96 | } 97 | return; 98 | } 99 | } else { 100 | keys = goog.object.getKeys(heap); 101 | values = goog.object.getValues(heap); 102 | } 103 | 104 | for (var i = 0; i < keys.length; i++) { 105 | this.insert(keys[i], values[i]); 106 | } 107 | }; 108 | 109 | 110 | /** 111 | * Retrieves and removes the root value of this heap. 112 | * @return {V} The value removed from the root of the heap. Returns 113 | * undefined if the heap is empty. 114 | */ 115 | goog.structs.Heap.prototype.remove = function() { 116 | var nodes = this.nodes_; 117 | var count = nodes.length; 118 | var rootNode = nodes[0]; 119 | if (count <= 0) { 120 | return undefined; 121 | } else if (count == 1) { 122 | goog.array.clear(nodes); 123 | } else { 124 | nodes[0] = nodes.pop(); 125 | this.moveDown_(0); 126 | } 127 | return rootNode.getValue(); 128 | }; 129 | 130 | 131 | /** 132 | * Retrieves but does not remove the root value of this heap. 133 | * @return {V} The value at the root of the heap. Returns 134 | * undefined if the heap is empty. 135 | */ 136 | goog.structs.Heap.prototype.peek = function() { 137 | var nodes = this.nodes_; 138 | if (nodes.length == 0) { 139 | return undefined; 140 | } 141 | return nodes[0].getValue(); 142 | }; 143 | 144 | 145 | /** 146 | * Retrieves but does not remove the key of the root node of this heap. 147 | * @return {V} The key at the root of the heap. Returns undefined if the 148 | * heap is empty. 149 | */ 150 | goog.structs.Heap.prototype.peekKey = function() { 151 | return this.nodes_[0] && this.nodes_[0].getKey(); 152 | }; 153 | 154 | 155 | /** 156 | * Moves the node at the given index down to its proper place in the heap. 157 | * @param {number} index The index of the node to move down. 158 | * @private 159 | */ 160 | goog.structs.Heap.prototype.moveDown_ = function(index) { 161 | var nodes = this.nodes_; 162 | var count = nodes.length; 163 | 164 | // Save the node being moved down. 165 | var node = nodes[index]; 166 | // While the current node has a child. 167 | while (index < (count >> 1)) { 168 | var leftChildIndex = this.getLeftChildIndex_(index); 169 | var rightChildIndex = this.getRightChildIndex_(index); 170 | 171 | // Determine the index of the smaller child. 172 | var smallerChildIndex = rightChildIndex < count && 173 | nodes[rightChildIndex].getKey() < nodes[leftChildIndex].getKey() ? 174 | rightChildIndex : leftChildIndex; 175 | 176 | // If the node being moved down is smaller than its children, the node 177 | // has found the correct index it should be at. 178 | if (nodes[smallerChildIndex].getKey() > node.getKey()) { 179 | break; 180 | } 181 | 182 | // If not, then take the smaller child as the current node. 183 | nodes[index] = nodes[smallerChildIndex]; 184 | index = smallerChildIndex; 185 | } 186 | nodes[index] = node; 187 | }; 188 | 189 | 190 | /** 191 | * Moves the node at the given index up to its proper place in the heap. 192 | * @param {number} index The index of the node to move up. 193 | * @private 194 | */ 195 | goog.structs.Heap.prototype.moveUp_ = function(index) { 196 | var nodes = this.nodes_; 197 | var node = nodes[index]; 198 | 199 | // While the node being moved up is not at the root. 200 | while (index > 0) { 201 | // If the parent is less than the node being moved up, move the parent down. 202 | var parentIndex = this.getParentIndex_(index); 203 | if (nodes[parentIndex].getKey() > node.getKey()) { 204 | nodes[index] = nodes[parentIndex]; 205 | index = parentIndex; 206 | } else { 207 | break; 208 | } 209 | } 210 | nodes[index] = node; 211 | }; 212 | 213 | 214 | /** 215 | * Gets the index of the left child of the node at the given index. 216 | * @param {number} index The index of the node to get the left child for. 217 | * @return {number} The index of the left child. 218 | * @private 219 | */ 220 | goog.structs.Heap.prototype.getLeftChildIndex_ = function(index) { 221 | return index * 2 + 1; 222 | }; 223 | 224 | 225 | /** 226 | * Gets the index of the right child of the node at the given index. 227 | * @param {number} index The index of the node to get the right child for. 228 | * @return {number} The index of the right child. 229 | * @private 230 | */ 231 | goog.structs.Heap.prototype.getRightChildIndex_ = function(index) { 232 | return index * 2 + 2; 233 | }; 234 | 235 | 236 | /** 237 | * Gets the index of the parent of the node at the given index. 238 | * @param {number} index The index of the node to get the parent for. 239 | * @return {number} The index of the parent. 240 | * @private 241 | */ 242 | goog.structs.Heap.prototype.getParentIndex_ = function(index) { 243 | return (index - 1) >> 1; 244 | }; 245 | 246 | 247 | /** 248 | * Gets the values of the heap. 249 | * @return {Array.} The values in the heap. 250 | */ 251 | goog.structs.Heap.prototype.getValues = function() { 252 | var nodes = this.nodes_; 253 | var rv = []; 254 | var l = nodes.length; 255 | for (var i = 0; i < l; i++) { 256 | rv.push(nodes[i].getValue()); 257 | } 258 | return rv; 259 | }; 260 | 261 | 262 | /** 263 | * Gets the keys of the heap. 264 | * @return {Array.} The keys in the heap. 265 | */ 266 | goog.structs.Heap.prototype.getKeys = function() { 267 | var nodes = this.nodes_; 268 | var rv = []; 269 | var l = nodes.length; 270 | for (var i = 0; i < l; i++) { 271 | rv.push(nodes[i].getKey()); 272 | } 273 | return rv; 274 | }; 275 | 276 | 277 | /** 278 | * Whether the heap contains the given value. 279 | * @param {V} val The value to check for. 280 | * @return {boolean} Whether the heap contains the value. 281 | */ 282 | goog.structs.Heap.prototype.containsValue = function(val) { 283 | return goog.array.some(this.nodes_, function(node) { 284 | return node.getValue() == val; 285 | }); 286 | }; 287 | 288 | 289 | /** 290 | * Whether the heap contains the given key. 291 | * @param {K} key The key to check for. 292 | * @return {boolean} Whether the heap contains the key. 293 | */ 294 | goog.structs.Heap.prototype.containsKey = function(key) { 295 | return goog.array.some(this.nodes_, function(node) { 296 | return node.getKey() == key; 297 | }); 298 | }; 299 | 300 | 301 | /** 302 | * Clones a heap and returns a new heap 303 | * @return {goog.structs.Heap} A new goog.structs.Heap with the same key-value 304 | * pairs. 305 | */ 306 | goog.structs.Heap.prototype.clone = function() { 307 | return new goog.structs.Heap(this); 308 | }; 309 | 310 | 311 | /** 312 | * The number of key-value pairs in the map 313 | * @return {number} The number of pairs. 314 | */ 315 | goog.structs.Heap.prototype.getCount = function() { 316 | return this.nodes_.length; 317 | }; 318 | 319 | 320 | /** 321 | * Returns true if this heap contains no elements. 322 | * @return {boolean} Whether this heap contains no elements. 323 | */ 324 | goog.structs.Heap.prototype.isEmpty = function() { 325 | return goog.array.isEmpty(this.nodes_); 326 | }; 327 | 328 | 329 | /** 330 | * Removes all elements from the heap. 331 | */ 332 | goog.structs.Heap.prototype.clear = function() { 333 | goog.array.clear(this.nodes_); 334 | }; 335 | -------------------------------------------------------------------------------- /goog/structs/node.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Generic immutable node object to be used in collections. 17 | * 18 | */ 19 | 20 | 21 | goog.provide('goog.structs.Node'); 22 | 23 | 24 | 25 | /** 26 | * A generic immutable node. This can be used in various collections that 27 | * require a node object for its item (such as a heap). 28 | * @param {K} key Key. 29 | * @param {V} value Vaue. 30 | * @constructor 31 | * @template K, V 32 | */ 33 | goog.structs.Node = function(key, value) { 34 | /** 35 | * The key. 36 | * @private {K} 37 | */ 38 | this.key_ = key; 39 | 40 | /** 41 | * The value. 42 | * @private {V} 43 | */ 44 | this.value_ = value; 45 | }; 46 | 47 | 48 | /** 49 | * Gets the key. 50 | * @return {K} The key. 51 | */ 52 | goog.structs.Node.prototype.getKey = function() { 53 | return this.key_; 54 | }; 55 | 56 | 57 | /** 58 | * Gets the value. 59 | * @return {V} The value. 60 | */ 61 | goog.structs.Node.prototype.getValue = function() { 62 | return this.value_; 63 | }; 64 | 65 | 66 | /** 67 | * Clones a node and returns a new node. 68 | * @return {goog.structs.Node.} A new goog.structs.Node with the same 69 | * key value pair. 70 | */ 71 | goog.structs.Node.prototype.clone = function() { 72 | return new goog.structs.Node(this.key_, this.value_); 73 | }; 74 | -------------------------------------------------------------------------------- /goog/structs/priorityqueue.js: -------------------------------------------------------------------------------- 1 | // Copyright 2006 The Closure Library Authors. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS-IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * @fileoverview Datastructure: Priority Queue. 17 | * 18 | * 19 | * This file provides the implementation of a Priority Queue. Smaller priorities 20 | * move to the front of the queue. If two values have the same priority, 21 | * it is arbitrary which value will come to the front of the queue first. 22 | */ 23 | 24 | // TODO(user): Should this rely on natural ordering via some Comparable 25 | // interface? 26 | 27 | 28 | goog.provide('goog.structs.PriorityQueue'); 29 | 30 | goog.require('goog.structs.Heap'); 31 | 32 | 33 | 34 | /** 35 | * Class for Priority Queue datastructure. 36 | * 37 | * @constructor 38 | * @extends {goog.structs.Heap} 39 | * @final 40 | */ 41 | goog.structs.PriorityQueue = function() { 42 | goog.structs.Heap.call(this); 43 | }; 44 | goog.inherits(goog.structs.PriorityQueue, goog.structs.Heap); 45 | 46 | 47 | /** 48 | * Puts the specified value in the queue. 49 | * @param {*} priority The priority of the value. A smaller value here means a 50 | * higher priority. 51 | * @param {*} value The value. 52 | */ 53 | goog.structs.PriorityQueue.prototype.enqueue = function(priority, value) { 54 | this.insert(priority, value); 55 | }; 56 | 57 | 58 | /** 59 | * Retrieves and removes the head of this queue. 60 | * @return {*} The element at the head of this queue. Returns 61 | * undefined if the queue is empty. 62 | */ 63 | goog.structs.PriorityQueue.prototype.dequeue = function() { 64 | return this.remove(); 65 | }; 66 | -------------------------------------------------------------------------------- /hub.js: -------------------------------------------------------------------------------- 1 | // hub script for dispatching simulation tasks to lots of servers 2 | 3 | console.log("THIS IS NOT STABLE") 4 | process.exit(1) 5 | 6 | var async = require('async') 7 | var sys = require('sys') 8 | 9 | var exec = require('child_process').exec; 10 | 11 | // todo just in case 12 | var escapeshell = function(cmd) { 13 | return '"'+cmd+'"'; 14 | }; 15 | 16 | function runRemoteCommand(host, cmd, out, cb, pr) { 17 | var r = Math.floor(Math.random() * 100000000) 18 | var f; 19 | if (out) 20 | f = "ssh -o \"StrictHostKeyChecking no\" ubuntu@" + host + " " + escapeshell(cmd) + " > " + (out+"-"+r) 21 | else 22 | f = "ssh -o \"StrictHostKeyChecking no\" ubuntu@" + host + " " + escapeshell(cmd); 23 | 24 | exec(f, function(err, stdout, stderr) { 25 | if (err) 26 | console.log(err) 27 | 28 | if (typeof pr != "undefined") 29 | process.stderr.write(stdout.replace(/\s+$/, "")) 30 | 31 | cb(null, null) 32 | }) 33 | } 34 | 35 | 36 | ///////////////////////////////////////////////////////////// 37 | 38 | hosts = [ 39 | ["ec2-23-20-147-173.compute-1.amazonaws.com", 8], 40 | ["ec2-54-205-85-161.compute-1.amazonaws.com", 8], 41 | ["ec2-54-204-121-80.compute-1.amazonaws.com", 8], 42 | ["ec2-54-227-3-199.compute-1.amazonaws.com", 8], 43 | ["ec2-54-205-28-168.compute-1.amazonaws.com", 8], 44 | ["ec2-54-204-252-104.compute-1.amazonaws.com", 8], 45 | ["ec2-54-221-142-38.compute-1.amazonaws.com", 8], 46 | ["ec2-54-196-176-198.compute-1.amazonaws.com", 8], 47 | ["ec2-54-196-157-128.compute-1.amazonaws.com", 8], 48 | ["ec2-54-242-127-177.compute-1.amazonaws.com", 8], 49 | ["ec2-54-227-221-158.compute-1.amazonaws.com", 8], 50 | ["ec2-54-196-48-111.compute-1.amazonaws.com", 8], 51 | ["ec2-67-202-55-118.compute-1.amazonaws.com", 8], 52 | ["ec2-54-204-73-120.compute-1.amazonaws.com", 8], 53 | ["ec2-23-20-85-175.compute-1.amazonaws.com", 8], 54 | ["ec2-54-196-173-2.compute-1.amazonaws.com", 8], 55 | ["ec2-54-211-248-182.compute-1.amazonaws.com", 8], 56 | ["ec2-54-196-136-116.compute-1.amazonaws.com", 8], 57 | ["ec2-54-234-230-115.compute-1.amazonaws.com", 8] 58 | ] 59 | 60 | 61 | tasks = [] 62 | 63 | for (var i=0;i<50;i++) { 64 | // every percent less than 50 but > 20 65 | for (var t=0;t<3;t++) { 66 | // 3 trials of each 67 | tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " normal", "/home/ubuntu/sim-"+i+"-normal-"+t]) 68 | tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " sybil", "/home/ubuntu/sim"+i+"-sybil-"+t]) 69 | tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " selfish", "/home/ubuntu/sim"+i+"-selfish-"+t]) 70 | tasks.push(["cd ebfull.github.io && node sim.js " + (i/100).toFixed(2) + " both", "/home/ubuntu/sim"+i+"-both-"+t]) 71 | } 72 | } 73 | 74 | ///////////////////////////////////////////////////////////// 75 | 76 | function doStuff() { 77 | 78 | var workers = async.queue(function(arg, cb) { 79 | var server = arg.server; 80 | 81 | var q = async.queue(function(nope, doneWithTasks) { 82 | var task; 83 | 84 | async.whilst(function() {return task = tasks.shift();}, function(taskDone) { 85 | console.log("dispatch (" + server[0] + "): " + task[0]) 86 | runRemoteCommand(server[0], task[0], task[1], function() { 87 | console.log("completed (" + server[0] + "): " + task[0]) 88 | taskDone() 89 | }); 90 | }, doneWithTasks); 91 | }, server[1]) 92 | 93 | q.drain = function() { 94 | host[3] = true; 95 | cb(); 96 | } 97 | 98 | for (var i=0;i 2 | 3 | 4 | 5 | simbit 6 | 7 | 62 | 63 | 64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | 74 | 79 | 87 | 88 | 89 | 92 | 95 | 98 | 99 | 100 | 112 | 120 | 121 |
75 |
76 | 77 |
78 |
80 | 86 |
90 |
91 |
96 | 97 |
101 |
102 | Elapsed: 0 seconds   103 |    104 |    105 |
106 | 107 |
108 | Speed: 109 |   (1.00x) 110 |
111 |
122 | 123 | 124 | 538 | 539 | 581 | 582 | 583 | -------------------------------------------------------------------------------- /network.js: -------------------------------------------------------------------------------- 1 | if (typeof goog == "undefined") { 2 | require('./goog/bootstrap/nodejs') 3 | goog.require("goog.structs.PriorityQueue") 4 | } 5 | 6 | var BitArray = require("./bit-array"); 7 | 8 | var topologySeed = Math.floor(Math.random() * 1000000000); 9 | 10 | function latency(a, b) { 11 | var min = 10 + Math.abs(((a*topologySeed)^(b*topologySeed)) % 300); 12 | var avgVariance = 15; 13 | 14 | return Math.floor((Math.log(1-Math.random())/-1) * (avgVariance)) + min 15 | } 16 | 17 | /* 18 | Events 19 | 20 | This object is used to coordinate events that occur in the simulation. It is a proxy 21 | for a priority queue. 22 | */ 23 | function Events() { 24 | this.heapBuckets = { 25 | "default":new goog.structs.PriorityQueue(), 26 | "probs":new goog.structs.PriorityQueue() 27 | }; 28 | } 29 | 30 | Events.prototype = { 31 | add: function(time, event, bucket) { 32 | if (typeof bucket == "undefined") 33 | bucket = "default" 34 | 35 | this.heapBuckets[bucket].insert(time, event); 36 | }, 37 | 38 | next: function(maxtime) { 39 | var best = Number.POSITIVE_INFINITY; 40 | var best_bucket = false; 41 | 42 | for (var b in this.heapBuckets) { 43 | var time = this.heapBuckets[b].peekKey(); 44 | 45 | if (typeof time == "undefined") 46 | continue; // bucket is empty 47 | 48 | if (time < best) { 49 | best = time; 50 | best_bucket = b; 51 | } 52 | } 53 | 54 | if (!best_bucket) 55 | return false; 56 | 57 | if (best > maxtime) 58 | return false; 59 | 60 | return {time:best, event:this.heapBuckets[best_bucket].dequeue()}; 61 | } 62 | } 63 | 64 | /* 65 | Interface: 66 | run(network) - runs an event against the Network 67 | delay - msec delay before the event should occur once it is committed to the network 68 | 69 | NodeEvent: runs a function against a node's state 70 | NodeMessageEvent: triggers a handler against a node's state, follows middleware paths 71 | NodeTickEvent: a repetitive function ran against a node's state. 72 | - if the function returns false, we do not run the tick again 73 | - the return of this function can override the delay if it is a number 74 | NodeProbabilisticTickEvent: a pool of events that can occur at any time, like mining 75 | */ 76 | 77 | function NodeEvent(delay, nid, f, ctx) { 78 | this.delay = delay; 79 | 80 | this.run = function(network) { 81 | if (typeof ctx == "undefined") 82 | ctx = network.nodes[nid] 83 | 84 | f.call(ctx); 85 | } 86 | } 87 | 88 | function NodeMessageEvent(from, nid, name, obj) { 89 | this.delay = latency(from, nid); 90 | 91 | this.run = function(network) { 92 | network.setLinkActivity(from, nid) 93 | 94 | network.nodes[nid].handle(from, name, obj) 95 | } 96 | } 97 | 98 | function NodeTickEvent(delay, f, ctx) { 99 | this.delay = delay; 100 | 101 | this.run = function(network) { 102 | var newDelay; 103 | if ((newDelay = f.call(ctx)) !== false) { 104 | if (typeof newDelay == "number") 105 | this.delay = newDelay; 106 | 107 | network.exec(this) 108 | } 109 | } 110 | } 111 | 112 | /**** 113 | @probability: used to describe probability of event firing every msec 114 | @event: function called 115 | @ctx: function context 116 | 117 | NodeProbabilisticTickEvent.ignore is used to disable an event if it's 118 | never going to occur again, thus avoiding a seek and destroy on the 119 | binary heap. 120 | ****/ 121 | function NodeProbabilisticTickEvent(probability, event, nid, ctx) { 122 | // The event will occur in this.delay msec 123 | this.delay = Math.floor(Math.log(1.0-Math.random())/-probability); 124 | this.ignore = false; 125 | 126 | this.run = function(network) { 127 | if (this.ignore) 128 | return false; 129 | 130 | if (typeof ctx == "undefined") 131 | ctx = network.nodes[nid] 132 | 133 | // fire event 134 | event.call(ctx) 135 | } 136 | } 137 | 138 | /* 139 | NodeState 140 | 141 | Has a bunch of helper functions for the node. 142 | */ 143 | 144 | function NodeState(node, network, id) { 145 | this.id = id; 146 | this.network = network; 147 | this.handlers = []; 148 | 149 | node.setup(this); 150 | } 151 | 152 | NodeState.prototype = { 153 | prob: function(label, p, f, ctx) { 154 | this.network.pregister(label, p, this.id, f, ctx) 155 | }, 156 | 157 | deprob: function(label) { 158 | this.network.depregister(label, this.id) 159 | }, 160 | 161 | setColor: function(color) { 162 | this.network.setColor(this.id, color); 163 | }, 164 | 165 | connect: function(remoteid) { 166 | this.network.connect(this.id, remoteid); 167 | }, 168 | 169 | disconnect: function(remoteid) { 170 | this.network.disconnect(this.id, remoteid); 171 | }, 172 | 173 | log: function(msg) { 174 | var str = "[" + this.now() + "]: " + this.id + ": " + msg; 175 | 176 | this.network.log(str) 177 | }, 178 | 179 | now: function() { 180 | return this.network.now; 181 | }, 182 | 183 | tick: function(delay, f, ctx) { 184 | if (typeof ctx == "undefined") 185 | ctx = this; 186 | 187 | this.network.exec(new NodeTickEvent(delay, f, ctx)) 188 | }, 189 | 190 | send: function(nid, name, obj) { 191 | this.network.exec(new NodeMessageEvent(this.id, nid, name, obj)) 192 | }, 193 | 194 | handle: function(from, name, obj) { 195 | if (typeof this.handlers[name] != "undefined") { 196 | return this.handlers[name](from, obj) 197 | } 198 | }, 199 | 200 | on: function(name, f, ctx) { 201 | if (typeof ctx == "undefined") 202 | ctx = this; 203 | 204 | if (typeof this.handlers[name] != "undefined") { 205 | var oldHandler = this.handlers[name]; 206 | this.handlers[name] = function(from, obj) {if (f.call(ctx, from, obj) !== false) oldHandler.call(ctx, from, obj);} 207 | } else { 208 | this.handlers[name] = function(from, obj) {return f.call(ctx, from, obj);}; 209 | } 210 | }, 211 | 212 | delay: function(delay, f, ctx) { 213 | this.network.exec(new NodeEvent(delay, this.id, f, ctx)) 214 | } 215 | } 216 | 217 | function Client() { 218 | this._use = []; 219 | this._init = false; 220 | } 221 | 222 | Client.prototype = { 223 | setup: function(node) { 224 | // run middleware 225 | for (var i=0;i objects 360 | 361 | var results = []; 362 | 363 | for (k in this.consensus.store) { 364 | var state = this.get(k); 365 | 366 | if (state.equals(v)) { 367 | results.push(state.__proto__); 368 | } 369 | } 370 | 371 | return results; 372 | }, 373 | create: function(obj) { 374 | return obj.init(this.consensus); 375 | }, 376 | rand: function() { 377 | return this.consensus.rand(); 378 | } 379 | }; 380 | 381 | /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 382 | 383 | function Network() { 384 | this.events = new Events(); // normal events 385 | this.pevents = {}; // probablistic event buckets 386 | if (typeof VISUALIZER != "undefined") { 387 | this.visualizer = VISUALIZER; 388 | } else { 389 | this.visualizer = false; 390 | } 391 | this.now = 0; 392 | this.maxrun = 0; 393 | 394 | this.nodes = []; 395 | this.nindex = 0; 396 | 397 | this._shared = {}; 398 | } 399 | 400 | Network.prototype = { 401 | Client: Client, 402 | // random data 403 | rand: function(name) { 404 | return Consensus.prototype.rand(); 405 | }, 406 | // grab a shared cache object 407 | shared: function(name) { 408 | if (typeof this._shared[name] == "undefined") { 409 | this._shared[name] = new Consensus(); 410 | } 411 | 412 | return this._shared[name].obtain(); 413 | }, 414 | 415 | log: function(str) { 416 | if (this.visualizer) 417 | this.visualizer.log(str) 418 | else 419 | console.log(str) 420 | }, 421 | 422 | // registers probablistic event 423 | pregister: function(label, p, nid, cb, ctx) { 424 | if (typeof this.pevents[nid + "-" + label] == "undefined") { 425 | this.pevents[nid + "-" + label] = new NodeProbabilisticTickEvent(p, cb, nid, ctx) 426 | this.exec(this.pevents[nid + "-" + label], "probs") 427 | } 428 | }, 429 | 430 | // deregisters a probablistic event 431 | depregister: function(label, nid) { 432 | if (typeof this.pevents[nid + "-" + label] != "undefined") { 433 | this.pevents[nid + "-" + label].ignore = true; 434 | delete this.pevents[nid + "-" + label]; 435 | } 436 | }, 437 | 438 | // sets the color of the node in the visualizer 439 | setColor: function(id, color) { 440 | if (typeof this.nodes[id] != "undefined") 441 | if (this.visualizer) { 442 | this.visualizer.setColor(this.nodes[id]._vid, color); 443 | } 444 | }, 445 | 446 | // could be used to show that network activity occurred between two nodes 447 | setLinkActivity: function(from, to) { 448 | if (typeof this.nodes[to] != "undefined") 449 | if (typeof this.nodes[from] != "undefined") 450 | if (this.visualizer) { 451 | this.visualizer.setLinkActivity("n" + this.nodes[from]._vid + "-n" + this.nodes[to]._vid, this.now); 452 | this.visualizer.setLinkActivity("n" + this.nodes[to]._vid + "-n" + this.nodes[from]._vid, this.now); 453 | } 454 | }, 455 | 456 | // places an event in the queue 457 | exec: function(e, bucket) { 458 | this.events.add(e.delay+this.now, e, bucket) 459 | }, 460 | 461 | // connects two nodes in the visualizer 462 | connect: function (a, b) { 463 | if (this.visualizer) { 464 | this.visualizer.connect(this.nodes[a]._vid, this.nodes[b]._vid, latency(this.nodes[a].id, this.nodes[b].id)); 465 | } 466 | }, 467 | 468 | // disconnects two nodes in the visualizer 469 | disconnect: function (a, b) { 470 | if (this.visualizer) { 471 | this.visualizer.disconnect(this.nodes[a]._vid, this.nodes[b]._vid); 472 | } 473 | }, 474 | 475 | // adds amt nodes using the node constructor parameter 476 | add: function(amt, node) { 477 | for (;amt>0;amt--) { 478 | var state = new NodeState(node, this, this.nindex); 479 | if (this.visualizer) 480 | state._vid = this.visualizer.addNode(); 481 | 482 | this.nodes[this.nindex] = state; 483 | this.nindex++; 484 | } 485 | }, 486 | 487 | // run buffer time (msec) worth of tasks 488 | run: function(msec, next) { 489 | this.maxrun = this.now + msec; 490 | 491 | if (typeof(DELAY_RUN) != "undefined") { 492 | // this is an async call 493 | DELAY_RUN.net = this; 494 | DELAY_RUN.cb = next; 495 | } else { 496 | this._run(msec) 497 | if (next) 498 | next.call(this); 499 | } 500 | }, 501 | 502 | _run: function(msec) { 503 | if (this.now >= this.maxrun) { 504 | if (DELAY_RUN) { 505 | if (DELAY_RUN.cb) { 506 | var cb = DELAY_RUN.cb; 507 | DELAY_RUN.cb = false; 508 | cb.call(this); 509 | } 510 | } 511 | return; 512 | } 513 | 514 | var max = Math.min(this.now + msec, this.maxrun); 515 | 516 | // actually run msec worth of shit 517 | while (e = this.events.next(max)) { 518 | this.now = e.time; 519 | e.event.run(this) 520 | } 521 | 522 | this.now = max; 523 | }, 524 | 525 | check: function(msec, f) { 526 | this.exec(new NodeTickEvent(msec, f, this)) 527 | }, 528 | 529 | stop: function() { 530 | this.maxrun = this.now; 531 | } 532 | } 533 | 534 | module.exports = new Network(); -------------------------------------------------------------------------------- /peermgr.js: -------------------------------------------------------------------------------- 1 | /* 2 | The PeerMgr is the inter-client behavior middleware for establishing connections 3 | with other peers, thus forming a network. 4 | 5 | (This peer manager is not intended to accurately mimic bitcoin yet.) 6 | 7 | Current behavior: every 1 second it checks to see if it has 'maxpeers'. If not, it attempts 8 | either to connect to an archived addr, or get addresses from other nodes it has contacted. 9 | Initially, the only archived addr is the bootstrap node, 0. Once the node has received 10 | maxpeers, it stops ticking. 11 | */ 12 | 13 | function PeerState(id, lastmessage) { 14 | this.id = id; 15 | this.lastmessage = lastmessage; 16 | this.active = false; 17 | this.locked = true; 18 | this.msgqueue = []; 19 | } 20 | 21 | function PeerMgr(self) { 22 | // attach to nodestate 'peers' property 23 | // self.peers = this; 24 | self.peermgr = this; 25 | 26 | this.freeze = false; 27 | this.ticking = false; 28 | this.peers = {}; // current established or attempted connections 29 | this.numpeers = 0; // the number of peers we have 30 | this.maxpeers = 8; // the max number of peers we can have 31 | if (self.id != 0) 32 | this.nodearchive = [new PeerState(0, self.now())]; // a node archived, initialized with a bootstrap node 33 | else 34 | this.nodearchive = [] 35 | 36 | var peerTick = function() { 37 | if (!this.freeze && (this.numpeers < this.maxpeers)) { 38 | // we need more peers 39 | 40 | // let's try connecting to a peer in our nodearchive, if there are any 41 | if (this.nodearchive.length) { 42 | var p = this.nodearchive.shift(); 43 | this.nodearchive.push(p); 44 | 45 | // try connecting to this node, fails if we're already trying/connected 46 | this.connect(p); 47 | } 48 | 49 | // if we have (active) connections, ask them for new peers 50 | if (this.numpeers) { 51 | var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] 52 | 53 | if (randomPeer.active) { 54 | this.getpeers(randomPeer.id) 55 | } 56 | } 57 | 58 | if (self.now() > 1000 * 1000) // after 1000 seconds, let's tick every 5 seconds instead 59 | return 5000; 60 | } else { 61 | self.peermgr.ticking = false; 62 | return false; // no more ticking necessary 63 | } 64 | } 65 | 66 | var startTicking = function() { 67 | if (!self.peermgr.ticking) { 68 | self.peermgr.ticking = true; 69 | 70 | self.tick(1000, peerTick, self.peermgr); 71 | } 72 | } 73 | 74 | this.numActive = function() { 75 | var n = 0; 76 | for (var p in this.peers) { 77 | if (this.peers[p].active) { 78 | n++; 79 | } 80 | } 81 | return n; 82 | } 83 | 84 | this.send = function(to, name, msg) { 85 | if (this.peers[to].active) { 86 | if (this.peers[to].locked) { 87 | this.peers[to].msgqueue.push({name:name,obj:msg}) 88 | } else { 89 | self.send(this.peers[to].id, "__peermsg", {name:name,obj:msg}) 90 | } 91 | } 92 | } 93 | 94 | this.each = function(cb) { 95 | for (var p in this.peers) { 96 | if (this.peers[p].active) 97 | cb.call(self, p) 98 | } 99 | } 100 | 101 | // sends a message to all active peers 102 | this.broadcast = function(name, msg) { 103 | for (var p in this.peers) { 104 | this.send(p, name, msg) 105 | } 106 | } 107 | 108 | // request peers from a remote node 109 | this.getpeers = function(p) { 110 | self.send(p, 'getpeers', {}); 111 | } 112 | 113 | // send a portion of our archived node list 114 | this.sendpeers = function(p) { 115 | var someNodes = this.nodearchive.slice(0, 15) 116 | if (someNodes.length == 0) { 117 | var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] 118 | if (randomPeer.active) 119 | someNodes = [randomPeer] 120 | } 121 | 122 | self.send(p, 'peerlist', someNodes); 123 | } 124 | 125 | // connect to a remote node (if we haven't already tried) 126 | this.connect = function(p) { 127 | if (self.id == p.id) 128 | return; // can't connect to ourselves! 129 | 130 | if (typeof this.peers[p.id] == "undefined") { 131 | this.peers[p.id] = p; 132 | this.numpeers += 1; 133 | self.send(p.id, 'connect', {}) 134 | } 135 | } 136 | 137 | // disconnect from a remote node 138 | this.disconnect = function(p) { 139 | if (typeof this.peers[p] != "undefined") { 140 | var peer = this.peers[p]; 141 | delete this.peers[p]; 142 | 143 | //if (peer.active) { 144 | self.send(p, 'disconnect', {}) 145 | self.disconnect(p); 146 | startTicking(); 147 | //} 148 | 149 | this.nodearchive.push(peer); 150 | this.numpeers -= 1; 151 | self.handle(p, "peermgr:disconnect", p) 152 | } 153 | } 154 | 155 | // accept a remote node's connection 156 | this.accept = function(p) { 157 | p.locked = true; // wait for an ack 158 | self.send(p.id, 'accept', {}) 159 | } 160 | 161 | // reject a remote node's connection 162 | this.reject = function(p) { 163 | var someNodes = this.nodearchive.slice(0, 15) 164 | if (someNodes.length == 0) { 165 | var randomPeer = this.peers[Object.keys(this.peers)[Math.floor(Math.random() * Object.keys(this.peers).length)]] 166 | if (randomPeer.active) 167 | someNodes = [this.randomPeer] 168 | } 169 | 170 | self.send(p.id, 'reject', someNodes) 171 | } 172 | 173 | this.onAck = function(from, o) { 174 | if (typeof this.peers[from] != "undefined") { 175 | this.peers[from].locked = false; 176 | this.peers[from].lastmessage = self.now() 177 | 178 | if (this.peers[from].msgqueue.length > 0) { 179 | var domsg = this.peers[from].msgqueue.shift(); 180 | this.peers[from].locked = true; 181 | self.send(this.peers[from].id, "__peermsg", {name:domsg.name,obj:domsg.obj}) 182 | } 183 | } 184 | } 185 | 186 | // processes a received message from another peer 187 | this.onReceive = function(from, o) { 188 | if (typeof this.peers[from] != "undefined" && this.peers[from].active) { 189 | self.send(from, 'ack', {}) 190 | self.handle(from, o.name, o.obj) 191 | this.peers[from].lastmessage = self.now(); 192 | } 193 | } 194 | 195 | // receive a peerlist message 196 | this.onPeerlist = function(from, obj) { 197 | // are we connected to this peer? 198 | if (this.peers[from] != "undefined") { 199 | // add these peers to our nodearchive 200 | // if we don't have them already 201 | 202 | for (var i=0;i-1 && CDTcomment privateHead.h) { 46 | this.log("adopting public chain") 47 | lead.length = 0; 48 | state = 0; 49 | 50 | privateHead = publicHead; 51 | } else if (privateHead.h > publicHead.h) { 52 | // We're ahead, but by how much? 53 | 54 | var ahead = privateHead.h - publicHead.h; 55 | 56 | if (ahead == 1) { 57 | if (state == 0) { 58 | this.log("ignoring 1 lead") 59 | // We're ahead by one, for the first time, so we'll wait and see if we can catch a block. 60 | } else { 61 | this.log("lead threatened; propagating entire private chain (" + lead.length + " blocks)") 62 | // We're only ahead by one now. Let's just propagate everything. 63 | lead.forEach(function(b) { 64 | this.inventory.relay(b.id, true); 65 | this.blockchain.chainstate.enter(b, true); 66 | }, this) 67 | 68 | lead.length = 0; 69 | state = 0; 70 | } 71 | } else { 72 | state = 1; 73 | // We're ahead by more than one, let's propagate our lead blocks UNTIL we reach the public 74 | // chainstate's head. 75 | 76 | var spliceOut = 0; 77 | 78 | lead.some(function(b) { 79 | if (b.h < publicHead.h) { 80 | this.inventory.relay(b.id, true); 81 | this.blockchain.chainstate.enter(b); 82 | spliceOut++; 83 | return false; 84 | } else if (b.h == publicHead.h) { 85 | this.inventory.relay(b.id, true); 86 | this.blockchain.chainstate.enter(b); 87 | spliceOut++; 88 | return true; 89 | } 90 | return true; 91 | }, this) 92 | 93 | this.log("propagated " + spliceOut + " of our " + lead.length + " lead (public is at " + publicHead.h + ")") 94 | 95 | lead.splice(0, spliceOut); 96 | } 97 | } 98 | } 99 | 100 | var newb = new this.blockchain.Block(privateHead, this.now(), this); 101 | 102 | return newb; 103 | }) 104 | 105 | this.on("miner:success", function(from, b) { 106 | b.time = this.now(); 107 | 108 | // Don't relay block yet. 109 | this.inventory.createObj("block", b) 110 | 111 | // Record the block. 112 | lead.push(b); 113 | 114 | return false; // hook the functionality of the blockchain 115 | }) 116 | } else { 117 | if (this.id == 0) { 118 | this.peermgr.maxpeers = 99; 119 | this.mine(selfishHashrate); 120 | } 121 | else 122 | this.mine((1-selfishHashrate)/99) 123 | } 124 | }) 125 | 126 | net.add(100, client) 127 | net.check(10000 * 1000, function() { 128 | net.nodes[90].blockchain.chainstate.head; 129 | 130 | var cur = net.nodes[90].blockchain.chainstate.head; 131 | 132 | var totalH = cur.h; 133 | var attackerRevenue = 0; 134 | 135 | while (cur) { 136 | if (cur.credit == 0) 137 | attackerRevenue++; 138 | 139 | cur = cur._prev(); 140 | } 141 | 142 | var perHour = (attackerRevenue / (net.now / (1000 * 60 * 60))).toFixed(2); 143 | 144 | net.log("Node 0 revenue: " + ((attackerRevenue / totalH) * 100).toFixed(2) + "%, " + perHour + " per hour; h=" + totalH + "; t=" + net.now + " msec") 145 | }) 146 | if (net.visualizer) { 147 | net.check(15000 * 1000, function() { 148 | var arrTimeSince = []; 149 | 150 | var mapBucket = {}; 151 | var cur = net.nodes[90].blockchain.chainstate.head; 152 | 153 | var last = -1; 154 | while (cur) { 155 | if (last >= 0) { 156 | arrTimeSince.push(last - cur.time) 157 | 158 | var timeSince = Math.floor(((last - cur.time)/1000) / 20); // buckets of 20 seconds 159 | 160 | if (!(timeSince in mapBucket)) { 161 | mapBucket[timeSince] = 0; 162 | } 163 | 164 | mapBucket[timeSince]++; 165 | } 166 | 167 | last = cur.time; 168 | cur = cur._prev(); 169 | } 170 | 171 | var data = []; 172 | 173 | for (i in mapBucket) { 174 | data.push([i*20,mapBucket[i]]) 175 | } 176 | 177 | net.visualizer.drawScatter(data); 178 | /* 179 | // calculate the average 180 | var mean = 0; 181 | arrTimeSince.forEach(function(n) { 182 | mean+=n; 183 | }) 184 | mean /= arrTimeSince.length; 185 | 186 | // calculate variance 187 | var variance = 0; 188 | arrTimeSince.forEach(function(n) { 189 | variance += Math.pow((n - mean), 2); 190 | }) 191 | variance /= arrTimeSince.length; 192 | 193 | // calculate stddev 194 | var stddev = Math.sqrt(variance); 195 | 196 | net.log("time between blocks: mean = " + (mean/1000).toFixed(2) + " seconds; stddev = " + (stddev/1000).toFixed(2) + ' seconds'); 197 | */ 198 | }) 199 | } 200 | net.run(Infinity) -------------------------------------------------------------------------------- /sim.js: -------------------------------------------------------------------------------- 1 | /* 2 | MinCen Simulation 3 | */ 4 | 5 | var net = require("./network"), 6 | peermgr = require("./peermgr"), 7 | btc = require("./btc"), 8 | client = new net.Client() 9 | 10 | btc.Blockchain.Block.prototype.target_avg_between_blocks = 60 * 1000; // one minute blocks 11 | btc.Blockchain.GenesisBlock.difficulty = 3700; 12 | 13 | client.use(peermgr) 14 | client.use(btc) 15 | 16 | var hashrate = 1; 17 | 18 | client.init(function() { 19 | var self = this; 20 | 21 | // mincen client specific stuff 22 | this.mapMasters = {}; 23 | var mapMasters = this.mapMasters; 24 | mapMasters[this.id] = {num:10, last:-1}; 25 | var shares = []; 26 | var shareh = -1; 27 | var master = this.id; 28 | var signedByPrev = {}; 29 | 30 | // pick a master given the share record of each master for the last 10 blocks 31 | var pickMaster = function() { 32 | var best = -1; 33 | var bestn = 0; 34 | 35 | for (m in mapMasters) { 36 | var _master = mapMasters[m]; 37 | 38 | if (_master.last < (self.miner.staged.h - 10)) { 39 | delete mapMasters[m]; 40 | continue; 41 | } 42 | 43 | if (_master.num > bestn) { 44 | bestn = _master.num; 45 | best = m; 46 | } 47 | } 48 | 49 | // select ourselves if we can't find one 50 | if (best == -1) { 51 | best = self.id; 52 | } 53 | 54 | // if the master changed, reset the sharelist 55 | if (master != best) { 56 | shares = []; 57 | master = best; 58 | } 59 | } 60 | 61 | // replace the restage function (called after a block is found, or another node finds a block) 62 | this.miner.restage = function() { 63 | if (self.miner.staged.h != self.miner.shareh) { 64 | shareh = self.miner.staged.h; 65 | shares = []; 66 | } 67 | 68 | self.miner.difficulty = self.miner.staged.difficulty; 69 | if (self.miner.enabled) { 70 | self.miner.stopMining() 71 | self.miner.startMining() 72 | } 73 | } 74 | 75 | // replace the miner:success function, handling the block we mined 76 | self.on("miner:success", function(from, b) { 77 | b.time = self.now(); 78 | b.transactions = self.mempool.getList(); 79 | pickMaster(); 80 | b.master = master; // delegate to the master we chose 81 | b.winner = Math.random() < (1/16); // 1/16 chance of being the block winner 82 | b.shares = shares.slice(0, 15); // 16 shares max 83 | 84 | if (b.winner) { 85 | // we won the race, let's get our block signed 86 | self.log("WINNER (" + b.id + ") at h=" + b.h); 87 | 88 | if (b.master == self.id) { 89 | // sign it ourselves because we're the master 90 | self.log("MASTER SIGN WINNER (" + b.id + ") at h=" + b.h) 91 | 92 | self.inventory.createObj("block", b); 93 | self.inventory.relay(b.id, true); 94 | 95 | self.blockchain.chainstate.enter(b); 96 | 97 | if (!(self.id in mapMasters)) { 98 | mapMasters[self.id] = {num:0,last:0}; 99 | } 100 | 101 | mapMasters[self.id].num++; 102 | mapMasters[self.id].last = b.h; 103 | 104 | signedByPrev[b.prev] = true; 105 | } else { 106 | // ship off an unsignedblock to get it signed by the master 107 | self.inventory.createObj("unsignedblock", {id:"unsigned_" + b.id, b:b}) 108 | self.inventory.relay("unsigned_" + b.id, true); 109 | } 110 | } else { 111 | // we found a share, let's get it in the hands of everyone else on our master 112 | self.log("SHARE (" + b.id + ") at h=" + b.h); 113 | 114 | if (b.master == self.id) { 115 | // sign it ourselves because we're the master 116 | self.log("MASTER SIGN SHARE (" + b.id + ") at h=" + b.h) 117 | 118 | self.inventory.createObj("shareack", {id:"ack_" + b.id, share:b, master:self.id}); 119 | 120 | self.inventory.relay("ack_" + b.id, true); 121 | } else { 122 | // ship it off to get signed 123 | self.inventory.createObj("share", {id:"share_" + b.id, share:b}); 124 | 125 | self.inventory.relay("share_" + b.id, true); 126 | } 127 | } 128 | 129 | 130 | 131 | return false; // hook the default miner code 132 | }, this.miner) 133 | 134 | // handling the share inventory object being sent over the wire 135 | self.on("obj:share", function(from, share) { 136 | if (share.share.master == self.id) { 137 | // sign the share we just received 138 | // TODO: strictly require the transactions to be in our UTXO 139 | self.log("MASTER SIGN SHARE (" + share.share.id + ") at h=" + share.share.h) 140 | // that's us! sign it 141 | self.inventory.createObj("shareack", {id:"ack_" + share.share.id, share:share.share, master:self.id}); 142 | 143 | self.inventory.relay("ack_" + share.share.id, true); 144 | } else { 145 | if (share.share.h == this.staged.h) { 146 | // this share interests the network, propagate it so it reaches a master 147 | self.inventory.relay(share.id, true); 148 | } 149 | } 150 | }, this.miner) 151 | 152 | // handling the shareack inventory object being sent over the wire 153 | self.on("obj:shareack", function(from, ack) { 154 | if (ack.share.h == this.staged.h) { 155 | // this shareack interests the network, propagate it so it reaches everyone who delegates to this master 156 | self.inventory.relay(ack.id, true); 157 | 158 | var share = ack.share; 159 | 160 | if (!(share.master in mapMasters)) { 161 | mapMasters[share.master] = {num:0,last:0}; 162 | } 163 | 164 | mapMasters[share.master].num++; 165 | mapMasters[share.master].last = share.h; 166 | 167 | // do i delegate to this master? 168 | if (share.master == master) { 169 | shares.push(share); 170 | } 171 | 172 | // pick a new master given the information, if necessary 173 | pickMaster(); 174 | } 175 | }, this.miner) 176 | 177 | // handling the unsignedblock inventory object being sent over the wire 178 | self.on("obj:unsignedblock", function(from, ub) { 179 | if (ub.b.master == self.id) { 180 | // that's us! sign it, if we haven't already signed another block with this prev 181 | if (!(ub.b.prev in signedByPrev)) { 182 | 183 | self.log("MASTER SIGN WINNER (" + ub.b.id + ") at h=" + ub.b.h); 184 | 185 | signedByPrev[ub.b.prev] = true; 186 | 187 | self.inventory.createObj("block", ub.b); 188 | 189 | self.inventory.relay(ub.b.id, true); 190 | 191 | self.blockchain.chainstate.enter(ub.b); 192 | 193 | self.miner.restage(); 194 | } 195 | } else { 196 | // just relay the unsigned block 197 | self.inventory.relay(ub.id, true); 198 | } 199 | }, this.miner) 200 | //////////////////////////////////////////////// 201 | var myHashrate = (hashrate / 2) * Math.random(); 202 | hashrate -= myHashrate; 203 | this.mine(myHashrate); 204 | //////////////////////////////////////////////// 205 | // adjust peering relationships based on who sends us blocks! 206 | 207 | var funpeers = {}; 208 | this.on("peermgr:connect", function(id) { 209 | funpeers[id] = {blocks:0}; 210 | return true; 211 | }) 212 | this.on("peermgr:disconnect", function(id) { 213 | delete funpeers[id]; 214 | return true; 215 | }) 216 | this.on("obj:block", function(from, b) { 217 | funpeers[from].blocks++; 218 | return true; 219 | }) 220 | this.tick(100 * 1000, function() { 221 | //if (this.peermgr.numActive() != this.peermgr.maxpeers) { 222 | // return true; 223 | //} 224 | // every 100 seconds boot off a poorly performing peer 225 | var worst_n = Infinity; 226 | var worst = null; 227 | 228 | var t = 0; 229 | 230 | for (var p in funpeers) { 231 | t += funpeers[p].blocks; 232 | if (funpeers[p].blocks < worst_n) { 233 | worst_n = funpeers[p].blocks; 234 | worst = p; 235 | } 236 | } 237 | 238 | if (t < 20) // we want a sample of 20 blocks 239 | return true; 240 | 241 | if (worst != null) { 242 | this.peermgr.disconnect(worst); 243 | } 244 | 245 | for (var p in funpeers) { 246 | funpeers[p].blocks = 0; 247 | } 248 | }) 249 | }) 250 | 251 | net.add(100, client) 252 | 253 | function getTimeWindow(start, size) { 254 | var timeWindow = [] 255 | var cur = start; 256 | 257 | var last = -1; 258 | while (cur && timeWindow.length < size) { 259 | if (last >= 0) { 260 | var timeSince = (last - cur.time)/1000; // in seconds 261 | timeWindow.push(timeSince); 262 | } 263 | 264 | last = cur.time; 265 | cur = cur._prev(); 266 | } 267 | return timeWindow; 268 | } 269 | 270 | function getAverage(timeWindow) { 271 | var average = 0; 272 | if (timeWindow.length > 0) { 273 | timeWindow.forEach(function(n) { 274 | average += n; 275 | }) 276 | average /= timeWindow.length; 277 | } 278 | return average; 279 | } 280 | 281 | var graphData = []; 282 | var checkpoint = -1; 283 | var shortWindow = 10; 284 | var longWindow = 100; 285 | if (net.visualizer) { 286 | net.check(1000 * 1000, function() { 287 | var sw = parseInt($("#shortwindow").val()); 288 | var lw = parseInt($("#longwindow").val()); 289 | if (sw != shortWindow || lw != longWindow) { 290 | shortWindow = sw; 291 | longWindow = lw; 292 | graphData = []; 293 | checkpoint = -1; 294 | } 295 | 296 | var head = net.nodes[90].blockchain.chainstate.head; 297 | var cur = head; 298 | var timeWindow = getTimeWindow(cur, head.h - checkpoint + longWindow); 299 | 300 | var temp = []; 301 | cur = head; 302 | var last = -1; 303 | while (cur && cur.h >= checkpoint) { 304 | if (last >= 0) { 305 | var timeSince = (last - cur.time)/1000; // in seconds 306 | var avShort = getAverage(timeWindow.slice(head.h-cur.h, head.h-cur.h+shortWindow)); 307 | var avLong = getAverage(timeWindow.slice(head.h-cur.h, head.h-cur.h+longWindow)); 308 | temp.push([cur.h, cur.difficulty, avShort, avLong]); 309 | } 310 | 311 | last = cur.time; 312 | cur = cur._prev(); 313 | } 314 | graphData = temp.concat(graphData); 315 | checkpoint = head.h; 316 | 317 | net.visualizer.drawGraph(graphData); 318 | }) 319 | } 320 | 321 | net.run(Infinity) 322 | --------------------------------------------------------------------------------