/** * @extend Ext.data.IdGenerator * @author Don Griffin * * 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', * idgen: 'uuid' * }); * * Ext.define('MyApp.data.MyModelY', { * extend: 'Ext.data.Model', * idgen: '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.IdGenerator.get('uuid').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.IdGenerator.get('uuid').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 for details. */ Ext.define('Ext.data.UuidGenerator', function () { var twoPow14 = Math.pow(2, 14), twoPow16 = Math.pow(2, 16), twoPow28 = Math.pow(2, 28), twoPow32 = Math.pow(2, 32); function toHex (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; } function rand (lo, hi) { var v = Math.random() * (hi - lo + 1); return Math.floor(v) + lo; } function split (bignum) { if (typeof(bignum) == 'number') { var hi = Math.floor(bignum / twoPow32); return { lo: Math.floor(bignum - hi * twoPow32), hi: hi }; } return bignum; } return { extend: 'Ext.data.IdGenerator', alias: 'idgen.uuid', id: 'uuid', // shared by default /** * @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. */ /** * @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. */ /** * @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, constructor: function() { var me = this; me.callParent(arguments); me.parts = []; me.init(); }, generate: function () { var me = this, parts = me.parts, ts = me.timestamp; /* 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] = toHex(ts.lo, 8); parts[1] = toHex(ts.hi & 0xFFFF, 4); parts[2] = toHex(((ts.hi >>> 16) & 0xFFF) | (me.version << 12), 4); parts[3] = toHex(0x80 | ((me.clockSeq >>> 8) & 0x3F), 2) + toHex(me.clockSeq & 0xFF, 2); parts[4] = toHex(me.salt.hi, 4) + toHex(me.salt.lo, 8); if (me.version == 4) { me.init(); // just regenerate all the random values... } else { // sequentially increment the timestamp... ++ts.lo; if (ts.lo >= twoPow32) { // if (overflow) ts.lo = 0; ++ts.hi; } } return parts.join('-').toLowerCase(); }, getRecId: function (rec) { return rec.getId(); }, /** * @private */ init: function () { var me = this, salt, time; if (me.version == 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 = rand(0, twoPow14-1); // we run this on every id generation... salt = me.salt || (me.salt = {}); time = me.timestamp || (me.timestamp = {}); // See RFC 4122 (Secion 4.4) salt.lo = rand(0, twoPow32-1); salt.hi = rand(0, twoPow16-1); time.lo = rand(0, twoPow32-1); time.hi = rand(0, twoPow28-1); } else { // this is run only once per-instance me.salt = split(me.salt); me.timestamp = split(me.timestamp); // Set multicast bit: "the least significant bit of the first octet of the // node ID" (nodeId = salt for this implementation): me.salt.hi |= 0x100; } }, /** * Reconfigures this generator given new config properties. */ reconfigure: function (config) { Ext.apply(this, config); this.init(); } }; }());