In Maria DB table I have two varbinary(16) fields that represent IPv6 addresses for the start and end of the IPv6 range.
I want to use PHP to loop between this range and generate each IPv6 address that's within the range. I tried to turn binary to decimal to do the loop and increase the decimal number, but the loop does not produce any iteration.
Any help?
//The $IpStart_v6_FromDb/$IpStart_v6_End Vars are produced with INET6_ATON MariaDB function
$IpStartBin = decbin($IpStart_v6_FromDb);
$IpEndBin = decbin($IpEnd_v6_FromDb);
$ArIpRange = array();
$ArIpRange[] = $IpStartBin;
$x=0;
for(;;)
{
if ($IpStartBin==$IpEndBin) break;
$tLastIpBin = $ArIpRange[$x];
$tNextIpBin = decbin( (bindec($tLastIpBin) + 1) );
if ($tNextIpBin==$IpEndBin) break;
$ArIpRange[] = $tNextIpBin;
$x++;
}
foreach ($ArIpRange as $v)
{
echo "<br>IP range item:<br>".base64_encode($v); //debug
}
[EDIT]
I'm embarrassed to say that I thought the length of an IPv6 address is 64 bits.
So, some simple troubleshooting or reading the manual would have told you that decbin expects an integer as input. So right off the bat you're getting a zero back for both variables.
Furthermore, even if you did correct that problem (by using bindec,) you're talking about 128 bit numbers which, unless you're from the future, are not something PHP can handle natively.
I'd suggest handling them as strings. First normalize them (fill in missing zeroes and replace :: with zeroes) using code from this answer, find and remove the matching prefix using code from this answer, and then deal with the rest by converting them to much smaller numbers.
And, as mentioned in the comments, make sure you don't try dealing with too big a range or you will make your server unhappy.
<?php
// https://stackoverflow.com/a/55521768/1255289
// Generate a list of IPv6 addresses within a given range
function expand_ipv6(string $ip): ?string
{
// https://stackoverflow.com/a/12095836/1255289
$hex = unpack("H*", inet_pton($ip))[1] ?? "";
return (strlen($hex) === 32) ? implode(":", str_split($hex, 4)) : null;
}
$IpStart_v6_FromDb = "2001:db8::1234";
$IpEnd_v6_FromDb = "2001:db8::1299";
$ip1 = expand_ipv6($IpStart_v6_FromDb);
$ip2 = expand_ipv6($IpEnd_v6_FromDb);
if ($ip1 === null || $ip2 === null) {
die;
}
// https://stackoverflow.com/a/35838357/1255289
// length is 39 to account for 7 colons
for ($i = 0; $i < 39 && $ip1[$i] === $ip2[$i]; $i++);
$ipv6_prefix = substr($ip1, 0, $i);
$ipv6_start = hexdec(substr($ip1, $i));
$ipv6_end = hexdec(substr($ip2, $i));
if (strlen($ipv6_prefix) < 26) {
// adjust this to requirements to prevent too large ranges
die;
}
for ($a = $ipv6_start; $a <= $ipv6_end; $a++) {
$hex = dechex($a);
printf("%s%s\n", $ipv6_prefix, implode(":", str_split($hex, 4)));
}
Related
I need to convert a really big integer that is represented as a string to a binary string (aka normal integer, but it is always bigger as normal php integer can hold) to efficiently store it in database and have a unique index on it.
The number comes from GMP (gmp_strval()) and may have different lengths, usually about 200-300 "characters" in it, so it never fits into PHP integer. The idea is to convert it into a binary string representing an integer, kind of big integer. Can I do it with PHP?
Sure you can do this.
Remember how to convert a decimal number to binary by hand.
look if the last digit is even (gives a 0) or odd (gives a 1)
subtract the 1, if you get one.
divide by 2. This have to be done digit by digit as in elementary school :-)
repeat this until your decimalnumber become zero.
I wrote a function for this
function strMod2(array $dec)
{
return ((int)end($dec)) % 2;
}
function strDivBy2(array $dec)
{
$res = [];
$carry = 0;
if($dec[0] == '0')
array_shift($dec);
$len = count($dec);
for($i = 0; $i < $len; $i++)
{
$num = $carry*10 + ((int)$dec[$i]);
$carry = $num % 2;
$num -= $carry;
$res[] = $num / 2;
}
return $res;
}
function dec2bin_str($dec)
{
$dec_arr = str_split($dec);
$bin_arr = [];
while(count($dec_arr) > 1 || $dec_arr[0] != 0)
{
array_unshift($bin_arr, strMod2($dec_arr));
$dec_arr = strDivBy2($dec_arr);
}
return implode($bin_arr);
}
You can use it as
echo dec2bin_str('5'); // '101'
echo dec2bin_str('146456131894613465451'); // '1111111000001111100101101000000000000010100001100010101100101101011'
Maybe this can be done faster by using a library for big integers.
Found Math_BigInteger library that can do it:
$a = new Math_BigInteger($intString);
$base256IntString = $a->toBytes();
https://github.com/pear/Math_BigInteger
Oauth requires a random 64-bit, unsigned number encoded as an ASCII string in decimal format. Can you guys help me achieve this with php?
Thanks
This was a really interesting problem (how to create the decimal representation of an arbitrary-length random number in PHP, using no optional extensions). Here's the solution:
Step 1: arbitrary-length random number
// Counts how many bits are needed to represent $value
function count_bits($value) {
for($count = 0; $value != 0; $value >>= 1) {
++$count;
}
return $count;
}
// Returns a base16 random string of at least $bits bits
// Actual bits returned will be a multiple of 4 (1 hex digit)
function random_bits($bits) {
$result = '';
$accumulated_bits = 0;
$total_bits = count_bits(mt_getrandmax());
$usable_bits = intval($total_bits / 8) * 8;
while ($accumulated_bits < $bits) {
$bits_to_add = min($total_bits - $usable_bits, $bits - $accumulated_bits);
if ($bits_to_add % 4 != 0) {
// add bits in whole increments of 4
$bits_to_add += 4 - $bits_to_add % 4;
}
// isolate leftmost $bits_to_add from mt_rand() result
$more_bits = mt_rand() & ((1 << $bits_to_add) - 1);
// format as hex (this will be safe)
$format_string = '%0'.($bits_to_add / 4).'x';
$result .= sprintf($format_string, $more_bits);
$accumulated_bits += $bits_to_add;
}
return $result;
}
At this point, calling random_bits(2048) will give you 2048 random bits as a hex-encoded string, no problem.
Step 2: arbitrary-precision base conversion
Math is hard, so here's the code:
function base_convert_arbitrary($number, $fromBase, $toBase) {
$digits = '0123456789abcdefghijklmnopqrstuvwxyz';
$length = strlen($number);
$result = '';
$nibbles = array();
for ($i = 0; $i < $length; ++$i) {
$nibbles[$i] = strpos($digits, $number[$i]);
}
do {
$value = 0;
$newlen = 0;
for ($i = 0; $i < $length; ++$i) {
$value = $value * $fromBase + $nibbles[$i];
if ($value >= $toBase) {
$nibbles[$newlen++] = (int)($value / $toBase);
$value %= $toBase;
}
else if ($newlen > 0) {
$nibbles[$newlen++] = 0;
}
}
$length = $newlen;
$result = $digits[$value].$result;
}
while ($newlen != 0);
return $result;
}
This function will work as advertised, for example try base_convert_arbitrary('ffffffffffffffff', 16, 10) == '18446744073709551615' and base_convert_arbitrary('10000000000000000', 16, 10) == '18446744073709551616'.
Putting it together
echo base_convert_arbitrary(random_bits(64), 16, 10);
You could use two 32-bit numbers, four 16-bit numbers, etc.
PHP has rand() and and mt_rand() but how many random bits they supply isn't specified by the standard (though they can be queried with the help of getrandmax() and mt_getrandmax(), respectively.)
So your safest simplest bet would be generating 64 random bits and setting them one by one.
As for working with 64-bit integers, I'd recommend using the GMP library as it has a good range of functions to help you out.
You could create a number, call 64 gmp_setbit()s on it with successive positions then convert it to a string using gmp_strval().
Are you building an OAuth adapter yourself? If so, you might want to reconsider. There are plenty of good OAuth libraries out there, including one from PECL, one in PEAR, another from the Zend Framework, and this other one hosted on Google Code. I've worked with the first three, and they're all pretty decent.
If you really want to do this yourself, you may face an issue. PHP can't think in 64-bit numbers unless it's compiled on a 64-bit platform or you have an advanced mathematics extension installed. This is going to make presenting a 64-bit number as a decimal very difficult. It looks like many of the libraries I linked above completely ignore the format requirement and simply work with a raw MD5 hash. Here's the code from ZF's adapter:
/**
* Generate nonce
*
* #return string
*/
public function generateNonce()
{
return md5(uniqid(rand(), true));
}
They look like they're getting away with this without interoperability issues.
I am trying to create a serial number checker.
Serial Numbers are in ranges
A87594 - A92778
AB34534 - AC23405
B23933 - C344444
I was able to get the numbers to work using range() for the first serial number example, I'm guessing I need to use explode() but I wasn't sure how to explode the letters into a variable and the numbers into a seperate variable.
if($_POST['submit']) {
$snum = $_POST['serial_number'];
// 1952
$jan01_jan07 = range(87594, 92478);
if (in_array($snum, $jan01_jan07)) {
echo 'You have a 1952 Widget';
}
else {
echo 'Your serial number is unknown';
}
}
You can try using strcmp, as it checks two strings, so you can check whether the incoming data is equal to or more than the lower bound and less than or equal to the upper bound, like this:
$data = $_POST['data']; // change this accordingly
if(strcmp($data, $lowerBound) >= 0 && strcmp($data, $upperBound) <= 0) {
// successful match
}
As strcmp returns -1, 0, 1 if $data is before, the same as and after $lowerBound (dictionary ordered), so this works for strings as well.
Try something along these lines:
preg_match('/([A-C]+)(\d+)/', $serial, $matches);
list(, $characters, $numbers) = $matches;
From there is kind of depends on the exact rules that govern your serials, something along these lines should do:
if ($characters == 'A' && 87594 <= $numbers && $numbers <= 92778) {
return true;
} else if ($characters == 'AB' …) ...
For an ecommerce site I want to generate a random coupon code that looks better than a randomly generated value. It should be a readable coupon code, all in uppercase with no special characters, only letters (A-Z) and numbers (0-9).
Since people might be reading this out / printing it elsewhere, we need to make this a simple-to-communicate value as well, perhaps 8-10 characters long.
Something like perhaps,
AHS3DJ6BW
B83JS1HSK
(I typed that, so it's not really that random)
$chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$res = "";
for ($i = 0; $i < 10; $i++) {
$res .= $chars[mt_rand(0, strlen($chars)-1)];
}
You can optimize this by preallocating the $res string and caching the result of strlen($chars)-1. This is left as an exercise to the reader, since probably you won't be generating thousands of coupons per second.
Try this:
substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, 10)
Why don't keep it simple?
<?php
echo strtoupper(uniqid());
?>
Always returns 13 character long uppercased random code.
You can use the coupon code generator PHP class file to generate N number of coupons and its customizable, with various options of adding own mask with own prefix and suffix. Simple PHP coupon code generator
Example:
coupon::generate(8); // J5BST6NQ
http://webarto.com/35/php-random-string-generator
Here you go.
function randr($j = 8){
$string = "";
for($i=0;$i < $j;$i++){
srand((double)microtime()*1234567);
$x = mt_rand(0,2);
switch($x){
case 0:$string.= chr(mt_rand(97,122));break;
case 1:$string.= chr(mt_rand(65,90));break;
case 2:$string.= chr(mt_rand(48,57));break;
}
}
return strtoupper($string); //to uppercase
}
If there are no security requirements for these, then you don't really need randomly generated codes. I would just use incremental IDs, such as those generated by whatever RDBMS you use. Optionally, if you have different types of coupons, you could prefix the codes with something, e.g.:
CX00019 QZ0001C
CX0001A QZ0001D
CX0001B QZ0001E
Alternately, you could even use dictionary words in the coupon, as such coupon codes are easier to remember and faster for users to type. Companies like Dreamhost use these for their promo codes, e.g.:
Promo60
NoSetupFee
YELLOWGORILLA82
Some of these are obviously human-created (which you might want to have the option of), but they can also be generated using a dictionary list. But even if they are randomly-generated nonsense phrases, the fact that the characters follow a logical pattern still makes it much more user-friendly than something like R7QZ8A92F1. So I would strongly advise against using the latter type of coupon codes just on the basis that they "look cool". Your customers will thank you.
$size = 12;
$string = strtoupper(substr(md5(time().rand(10000,99999)), 0, $size));
function generateCouponCode($length = 8) {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$ret = '';
for($i = 0; $i < $length; ++$i) {
$random = str_shuffle($chars);
$ret .= $random[0];
}
return $ret;
}
you can find a lot of function in php rand manual
http://php.net/manual/en/function.rand.php
i like this one
<?php
//To Pull 8 Unique Random Values Out Of AlphaNumeric
//removed number 0, capital o, number 1 and small L
//Total: keys = 32, elements = 33
$characters = array(
"A","B","C","D","E","F","G","H","J","K","L","M",
"N","P","Q","R","S","T","U","V","W","X","Y","Z",
"1","2","3","4","5","6","7","8","9");
//make an "empty container" or array for our keys
$keys = array();
//first count of $keys is empty so "1", remaining count is 1-7 = total 8 times
while(count($keys) < 8) {
//"0" because we use this to FIND ARRAY KEYS which has a 0 value
//"-1" because were only concerned of number of keys which is 32 not 33
//count($characters) = 33
$x = mt_rand(0, count($characters)-1);
if(!in_array($x, $keys)) {
$keys[] = $x;
}
}
foreach($keys as $key){
$random_chars .= $characters[$key];
}
echo $random_chars;
?>
$length = 9;
$code = (strtoupper(substr(md5(time()), 0, $length)));
Just Write
$voucher_no = date('ymd') . rand(1000, 9999);
while(SapItem::where('voucher_no', $voucher_no)->exists()){
$voucher_no = date('ymd') . rand(1000, 9999);
}
Output: 2204171447
There's a blog post comment on codinghorror.com by Paul Jungwirth which includes a little programming task:
You have the numbers 123456789, in that order. Between each number, you must insert either nothing, a plus sign, or a multiplication sign, so that the resulting expression equals 2001. Write a program that prints all solutions. (There are two.)
Bored, I thought, I'd have a go, but I'll be damned if I can get a result for 2001. I think the code below is sound and I reckon that there are zero solutions that result in 2001. According to my code, there are two solutions for 2002. Am I right or am I wrong?
/**
* Take the numbers 123456789 and form expressions by inserting one of ''
* (empty string), '+' or '*' between each number.
* Find (2) solutions such that the expression evaluates to the number 2001
*/
$input = array(1,2,3,4,5,6,7,8,9);
// an array of strings representing 8 digit, base 3 numbers
$ops = array();
$numOps = sizeof($input)-1; // always 8
$mask = str_repeat('0', $numOps); // mask of 8 zeros for padding
// generate the ops array
$limit = pow(3, $numOps) -1;
for ($i = 0; $i <= $limit; $i++) {
$s = (string) $i;
$s = base_convert($s, 10, 3);
$ops[] = substr($mask, 0, $numOps - strlen($s)) . $s;
}
// for each element in the ops array, generate an expression by inserting
// '', '*' or '+' between the numbers in $input. e.g. element 11111111 will
// result in 1+2+3+4+5+6+7+8+9
$limit = sizeof($ops);
$stringResult = null;
$numericResult = null;
for ($i = 0; $i < $limit; $i++) {
$l = $numOps;
$stringResult = '';
$numericResult = 0;
for ($j = 0; $j <= $l; $j++) {
$stringResult .= (string) $input[$j];
switch (substr($ops[$i], $j, 1)) {
case '0':
break;
case '1':
$stringResult .= '+';
break;
case '2':
$stringResult .= '*';
break;
default :
}
}
// evaluate the expression
// split the expression into smaller ones to be added together
$temp = explode('+', $stringResult);
$additionElems = array();
foreach ($temp as $subExpressions)
{
// split each of those into ones to be multiplied together
$multplicationElems = explode('*', $subExpressions);
$working = 1;
foreach ($multplicationElems as $operand) {
$working *= $operand;
}
$additionElems[] = $working;
}
$numericResult = 0;
foreach($additionElems as $operand)
{
$numericResult += $operand;
}
if ($numericResult == 2001) {
echo "{$stringResult}\n";
}
}
Further down the same page you linked to.... =)
"Paul Jungwirth wrote:
You have the numbers 123456789, in
that order. Between each number, you
must insert either nothing, a plus
sign, or a multiplication sign, so
that the resulting expression equals
2001. Write a program that prints all solutions. (There are two.)
I think you meant 2002, not 2001. :)
(Just correcting for anyone else like
me who obsessively tries to solve
little "practice" problems like this
one, and then hit Google when their
result doesn't match the stated
answer. ;) Damn, some of those Perl
examples are ugly.)"
The number is 2002.
Recursive solution takes eleven lines of JavaScript (excluding string expression evaluation, which is a standard JavaScript function, however it would probably take another ten or so lines of code to roll your own for this specific scenario):
function combine (digit,exp) {
if (digit > 9) {
if (eval(exp) == 2002) alert(exp+'=2002');
return;
}
combine(digit+1,exp+'+'+digit);
combine(digit+1,exp+'*'+digit);
combine(digit+1,exp+digit);
return;
}
combine(2,'1');