| 4 | | use Digest::SHA qw/ sha1_hex /; |
| 5 | | use base qw/ Class::Accessor::Fast /; |
| | 6 | use Math::BigInt try => 'GMP'; |
| | 7 | |
| | 8 | no warnings qw(deprecated); # for fields |
| | 9 | |
| | 10 | use vars qw{$VERSION}; |
| | 11 | $VERSION = "0.02"; |
| | 12 | |
| | 13 | use fields qw(host_id start_time current_time min_id max_id ids); |
| | 14 | |
| | 15 | use constant EPOCH_OFFSET => 946684800; # Sat, Jan 1 2000 00:00 GMT |
| | 16 | |
| | 17 | use constant HOST_ID_BITS => 16; |
| | 18 | use constant TIME_BITS => 36; |
| | 19 | use constant SERIAL_BITS => 64 - HOST_ID_BITS - TIME_BITS; |
| | 20 | |
| | 21 | use constant TIME_SHIFT => HOST_ID_BITS + SERIAL_BITS; |
| | 22 | use constant SERIAL_SHIFT => HOST_ID_BITS; |
| | 23 | |
| | 24 | use constant SERIAL_INCREMENT => Math::BigInt->new(1) << SERIAL_SHIFT; |
| | 25 | |
| | 26 | use constant HOST_ID_MAX => (Math::BigInt->new(1) << HOST_ID_BITS) - 1; |
| | 27 | use constant TIME_MAX => (Math::BigInt->new(1) << TIME_BITS) - 1; |
| | 28 | use constant TIME_MAX_SHIFTED => TIME_MAX << TIME_SHIFT; |
| | 29 | use constant SERIAL_MAX => (Math::BigInt->new(1) << SERIAL_BITS) - 1; |
| | 30 | use constant SERIAL_MAX_SHIFTED => SERIAL_MAX << SERIAL_SHIFT; |
| | 31 | |
| | 32 | |
| | 33 | sub new { |
| | 34 | my $self = shift; |
| | 35 | $self = fields::new( $self ) unless ref $self; |
| | 36 | |
| | 37 | my $host_id = shift; |
| | 38 | if( !$host_id ) { |
| | 39 | $host_id = int( rand( HOST_ID_MAX ) ); |
| | 40 | } elsif( $host_id < 0 || $host_id > HOST_ID_MAX ) { |
| | 41 | warn __PACKAGE__ . ": host ID $host_id is not in range of [0," . HOST_ID_MAX . "]\n"; |
| | 42 | return; |
| | 43 | } |
| | 44 | |
| | 45 | $self->{ host_id } = $host_id; |
| | 46 | $self->{ start_time } = time; |
| | 47 | $self->{ current_time } = 0; |
| | 48 | $self->{ ids } = {}; |
| | 49 | $self->_sync(); |
| | 50 | |
| | 51 | return $self; |
| | 52 | } |
| | 53 | |
| | 54 | |
| | 55 | sub _sync { |
| | 56 | my $self = shift; |
| | 57 | my $time = time; |
| | 58 | return if( $self->{ current_time } == $time ); # FIXME: check for clock skew |
| | 59 | $self->{ current_time } = $time; |
| | 60 | $self->{ min_id } = $self->_make_id( 0 ) unless( $self->{ min_id } ); |
| | 61 | $self->{ max_id } = $self->_make_id( SERIAL_MAX ); |
| | 62 | } |
| | 63 | |
| | 64 | |
| | 65 | sub _make_id { |
| | 66 | my $self = shift; |
| | 67 | my $serial = shift || 0; |
| | 68 | return |
| | 69 | ((Math::BigInt->new( $self->{ current_time } - EPOCH_OFFSET )) << TIME_SHIFT) |
| | 70 | | (Math::BigInt->new($serial) << SERIAL_SHIFT) | $self->{ host_id }; |
| | 71 | } |
| | 72 | |