/** * @author Tommy Maintz * * This class generates UUID's according to RFC 4122. This class has a default id property. * This means that a single instance is shared unless the id property is overridden. Thus, * two {@link Ext.data.Model} instances configured like the following share one generator: * * Ext.define('MyApp.data.MyModelX', { * extend: 'Ext.data.Model', * config: { * identifier: 'uuid' * } * }); * * Ext.define('MyApp.data.MyModelY', { * extend: 'Ext.data.Model', * config: { * identifier: 'uuid' * } * }); * * This allows all models using this class to share a commonly configured instance. * * # Using Version 1 ("Sequential") UUID's * * If a server can provide a proper timestamp and a "cryptographic quality random number" * (as described in RFC 4122), the shared instance can be configured as follows: * * Ext.data.identifier.Uuid.Global.reconfigure({ * version: 1, * clockSeq: clock, // 14 random bits * salt: salt, // 48 secure random bits (the Node field) * timestamp: ts // timestamp per Section 4.1.4 * }); * * // or these values can be split into 32-bit chunks: * * Ext.data.identifier.Uuid.Global.reconfigure({ * version: 1, * clockSeq: clock, * salt: { lo: saltLow32, hi: saltHigh32 }, * timestamp: { lo: timestampLow32, hi: timestamptHigh32 } * }); * * This approach improves the generator's uniqueness by providing a valid timestamp and * higher quality random data. Version 1 UUID's should not be used unless this information * can be provided by a server and care should be taken to avoid caching of this data. * * See [http://www.ietf.org/rfc/rfc4122.txt](http://www.ietf.org/rfc/rfc4122.txt) for details. */ Ext.define('Ext.data.identifier.Uuid', { extend: 'Ext.data.identifier.Simple', alias: 'data.identifier.uuid', /** * Provides a way to determine if this identifier supports creating unique IDs. Proxies like {@link Ext.data.proxy.LocalStorage} * need the identifier to create unique IDs and will check this property. * @property isUnique * @type Boolean * @private */ isUnique: true, config: { /** * The id for this generator instance. By default all model instances share the same * UUID generator instance. By specifying an id other then 'uuid', a unique generator instance * will be created for the Model. */ id: undefined, /** * @property {Number/Object} salt * When created, this value is a 48-bit number. For computation, this value is split * into 32-bit parts and stored in an object with `hi` and `lo` properties. */ salt: null, /** * @property {Number/Object} timestamp * When created, this value is a 60-bit number. For computation, this value is split * into 32-bit parts and stored in an object with `hi` and `lo` properties. */ timestamp: null, /** * @cfg {Number} version * The Version of UUID. Supported values are: * * * 1 : Time-based, "sequential" UUID. * * 4 : Pseudo-random UUID. * * The default is 4. */ version: 4 }, applyId: function(id) { if (id === undefined) { return Ext.data.identifier.Uuid.Global; } return id; }, constructor: function() { var me = this; me.callParent(arguments); me.parts = []; me.init(); }, /** * Reconfigures this generator given new config properties. */ reconfigure: function(config) { this.setConfig(config); this.init(); }, generate: function () { var me = this, parts = me.parts, version = me.getVersion(), salt = me.getSalt(), time = me.getTimestamp(); /* The magic decoder ring (derived from RFC 4122 Section 4.2.2): +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time_low | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | time_mid | ver | time_hi | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |res| clock_hi | clock_low | salt 0 |M| salt 1 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | salt (2-5) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ time_mid clock_hi (low 6 bits) time_low | time_hi |clock_lo | | | || salt[0] | | | || | salt[1..5] v v v vv v v 0badf00d-aced-1def-b123-dfad0badbeef ^ ^ ^ version | multicast (low bit) | reserved (upper 2 bits) */ parts[0] = me.toHex(time.lo, 8); parts[1] = me.toHex(time.hi & 0xFFFF, 4); parts[2] = me.toHex(((time.hi >>> 16) & 0xFFF) | (version << 12), 4); parts[3] = me.toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) + me.toHex(me.clockSeq & 0xFF, 2); parts[4] = me.toHex(salt.hi, 4) + me.toHex(salt.lo, 8); if (version == 4) { me.init(); // just regenerate all the random values... } else { // sequentially increment the timestamp... ++time.lo; if (time.lo >= me.twoPow32) { // if (overflow) time.lo = 0; ++time.hi; } } return parts.join('-').toLowerCase(); }, /** * @private */ init: function () { var me = this, salt = me.getSalt(), time = me.getTimestamp(); if (me.getVersion() == 4) { // See RFC 4122 (Secion 4.4) // o If the state was unavailable (e.g., non-existent or corrupted), // or the saved node ID is different than the current node ID, // generate a random clock sequence value. me.clockSeq = me.rand(0, me.twoPow14-1); if (!salt) { salt = {}; me.setSalt(salt); } if (!time) { time = {}; me.setTimestamp(time); } // See RFC 4122 (Secion 4.4) salt.lo = me.rand(0, me.twoPow32-1); salt.hi = me.rand(0, me.twoPow16-1); time.lo = me.rand(0, me.twoPow32-1); time.hi = me.rand(0, me.twoPow28-1); } else { // this is run only once per-instance me.setSalt(me.split(me.getSalt())); me.setTimestamp(me.split(me.getTimestamp())); // Set multicast bit: "the least significant bit of the first octet of the // node ID" (nodeId = salt for this implementation): me.getSalt().hi |= 0x100; } }, /** * Some private values used in methods on this class. * @private */ twoPow14: Math.pow(2, 14), twoPow16: Math.pow(2, 16), twoPow28: Math.pow(2, 28), twoPow32: Math.pow(2, 32), /** * Converts a value into a hexadecimal value. Also allows for a maximum length * of the returned value. * @param {String} value * @param {Number} length * @private */ toHex: function(value, length) { var ret = value.toString(16); if (ret.length > length) { ret = ret.substring(ret.length - length); // right-most digits } else if (ret.length < length) { ret = Ext.String.leftPad(ret, length, '0'); } return ret; }, /** * Generates a random value with between a low and high. * @param {Number} low * @param {Number} high * @private */ rand: function(low, high) { var v = Math.random() * (high - low + 1); return Math.floor(v) + low; }, /** * Splits a number into a low and high value. * @param {Number} bignum * @private */ split: function(bignum) { if (typeof(bignum) == 'number') { var hi = Math.floor(bignum / this.twoPow32); return { lo: Math.floor(bignum - hi * this.twoPow32), hi: hi }; } return bignum; } }, function() { this.Global = new this({ id: 'uuid' }); });