Determine if a Bitcoin wallet address is "valid" - c#

I know it's possible to verify Bitcoin wallet addresses with Regex (^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$) - but this is not 100% accurate and allows invalid addresses to be detected as valid.
Is there publicly available C# algorithm that can verify bitcoin wallet addresses? I've been Googling for one, but couldn't find anything.

Yes, the Bitcoin-Address-Utility project is an open-source C# tool that includes this kind of verification. In particular, look at Casascius.Bitcoin.Util.Base58CheckToByteArray().

I pieced together a simple version of Casascius.Bitcoin.Util.Base58CheckToByteArray() that works with dotnet core 2.0 - The only reference is to -->Org.BouncyCastle.Crypto.Digests;
public class Validator
{
public static bool IsValidAddress(string Address)
{
byte[] hex = Base58CheckToByteArray(Address);
if (hex == null || hex.Length != 21)
return false;
else
return true;
}
public static byte[] Base58CheckToByteArray(string base58)
{
byte[] bb = Base58.ToByteArray(base58);
if (bb == null || bb.Length < 4) return null;
Sha256Digest bcsha256a = new Sha256Digest();
bcsha256a.BlockUpdate(bb, 0, bb.Length - 4);
byte[] checksum = new byte[32];
bcsha256a.DoFinal(checksum, 0);
bcsha256a.BlockUpdate(checksum, 0, 32);
bcsha256a.DoFinal(checksum, 0);
for (int i = 0; i < 4; i++)
{
if (checksum[i] != bb[bb.Length - 4 + i]) return null;
}
byte[] rv = new byte[bb.Length - 4];
Array.Copy(bb, 0, rv, 0, bb.Length - 4);
return rv;
}
}
} - borrowed from above
public class Base58
{
/// <summary>
/// Converts a base-58 string to a byte array, returning null if it wasn't valid.
/// </summary>
public static byte[] ToByteArray(string base58)
{
Org.BouncyCastle.Math.BigInteger bi2 = new Org.BouncyCastle.Math.BigInteger("0");
string b58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
foreach (char c in base58)
{
if (b58.IndexOf(c) != -1)
{
bi2 = bi2.Multiply(new Org.BouncyCastle.Math.BigInteger("58"));
bi2 = bi2.Add(new Org.BouncyCastle.Math.BigInteger(b58.IndexOf(c).ToString()));
}
else
{
return null;
}
}
byte[] bb = bi2.ToByteArrayUnsigned();
// interpret leading '1's as leading zero bytes
foreach (char c in base58)
{
if (c != '1') break;
byte[] bbb = new byte[bb.Length + 1];
Array.Copy(bb, 0, bbb, 1, bb.Length);
bb = bbb;
}
return bb;
}
public static string FromByteArray(byte[] ba)
{
Org.BouncyCastle.Math.BigInteger addrremain = new Org.BouncyCastle.Math.BigInteger(1, ba);
Org.BouncyCastle.Math.BigInteger big0 = new Org.BouncyCastle.Math.BigInteger("0");
Org.BouncyCastle.Math.BigInteger big58 = new Org.BouncyCastle.Math.BigInteger("58");
string b58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
string rv = "";
while (addrremain.CompareTo(big0) > 0)
{
int d = Convert.ToInt32(addrremain.Mod(big58).ToString());
addrremain = addrremain.Divide(big58);
rv = b58.Substring(d, 1) + rv;
}
// handle leading zeroes
foreach (byte b in ba)
{
if (b != 0) break;
rv = "1" + rv;
}
return rv;
}
}
} - Tests
[TestClass]
public class ValidatorTests
{
[TestMethod]
public void IsValidAddress_Test_AbnCoin()
{
var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
Assert.IsTrue(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_BitCoin()
{
var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
Assert.IsTrue(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_BitCoinTestnet()
{
var Address = "mpMwtvqaLQ4rCJsnoceAoLShKb4inV8uUi";
Assert.IsTrue(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_BitCoinGold()
{
var Address = "GRiDm3LEjXAMMJhWaYqN8nSjuU7PSqZMUe";
Assert.IsTrue(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_Dash()
{
var Address = "Xb9Edf28eYR9RRDwj7MBBVBc5vgGgT2vLV";
Assert.IsTrue(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_Litecoin()
{
var Address = "LUdpZosHDP3M97ZSfvj3p1qygNFMNpXBr3";
Assert.IsTrue(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_False_TooShort()
{
var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSm";
Assert.IsFalse(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_False_TooLong()
{
var Address = "1QF4NgxgF86SH4dizN4JPHMprWBHbKdSmJS";
Assert.IsFalse(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_Test_False_BadChecksum()
{
var Address = "1QF5NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
Assert.IsFalse(Validator.IsValidAddress(Address));
}
[TestMethod]
public void IsValidAddress_False_NotBase58()
{
var Address = "lQF4NgxgF86SH4dizN4JPHMprWBHbKdSmJ";
Assert.IsFalse(Validator.IsValidAddress(Address));
}
}
}

tl;dr:
Had the same problem, therefore built something that suits my (and hopefully your) needs:
https://github.com/Sofoca/CoinUtils
My specific requirements were…
Support for Bitcoin and Litecoin (and in the future probably other similar Altcoins)
Support for all address types (P2SH and P2PKH) and encodings (Base58 and Bech32)
Preferably no external (think NuGet) dependencies
While none of the above mentioned alternatives met all those I took some inspiration from previous answers and referenced projects. Thanks for that!
Hope this helps those searching for a complete yet lightweight solution to this problem.

Here is Util.Bitcoin Git repository that contains only code needed for offline verification of BTC Wallet address.
Code is extracted from that Bitcoin-Address-Utility project referenced by other answer, but this repository contains necessary DLLs plus it is class project and not Windows app, so it can be directly referenced.

Related

What is the proper way to make a queue inside a class?

I'm trying to make a queue of orders for my clients.
But the last added value replace all the values ​​in the queue.
Debbuging the code i've see that when a value is enqueued it overrides all the other values in the queue.
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting...");
byte[] dataInit = new byte[] { 0x00 };
Client clientTest = new Client();
for (int i = 0; i <= 5; i++)
{
dataInit[0]++;
Console.WriteLine("Adding Order - i = {0}; Order: {1}.", i, BitConverter.ToString(dataInit));
clientTest.AddOrder(dataInit);
Console.WriteLine("Peeking Order - i = {0}; Order: {1}", i, BitConverter.ToString(clientTest.PeekOrder()));
}
for (int i = 0; i <= 5; i++)
{
Console.WriteLine("Removing order - i = {0}; Order: {1}.", i, BitConverter.ToString(clientTest.RemoveOrder()));
}
Console.WriteLine("Press Any Key...");
Console.Read();
}
class ClientOrder
{
public byte[] Order;
public ClientOrder(byte[] data)
{
Order = data;
}
}
class Client
{
public Queue<ClientOrder> ClientOrders = new Queue<ClientOrder>();
public void AddOrder(byte[] orderToAdd)
{
ClientOrders.Enqueue(new ClientOrder(orderToAdd));
}
public byte[] RemoveOrder()
{
ClientOrder toReturn = ClientOrders.Dequeue();
return toReturn.Order;
}
public byte[] PeekOrder()
{
ClientOrder toReturn = ClientOrders.Peek();
return toReturn.Order;
}
}
}
i've expected that the queue was in order [0-6]. but the actual output is {06,06,06,06,06,06} (The last value added).
You are actually sharing the same reference to byte[] and then with every Enqueue you actually replace all the elements in the queue as they all reference the same array. You should make a copy when creating ClientOrder. The easy way is using Linq, but there are also other possibilities.
public ClientOrder(byte[] data)
{
Order = data.ToArray();
}
or other way around as Jeff said

PostGIS Entity Framework

I am trying to use Npgsql and Npgsql.EntityFramework with a geography data type. I can write data to the database, but I'm unable to retrieve it. I am not sure if this is even possible, so I would appreciate any help.
The class that represents the table looks like this:
internal class Geo
{
[Key, Column("geo_test_id")]
public int GeoId { get; set; }
[Column("geography")]
public byte[] Geography { get; set; }
}
Data insertion is done like this:
var geog1 = System.Data.Spatial.DbGeography.FromText("POINT(-118.4079 33.9434)");
var geog2 = System.Data.Spatial.DbGeography.FromText("POINT(2.5559 49.0083)");
using (var db = new Database.MyDbContext())
{
db.Geos.Add(new Database.Geo() { Geography = geog1.AsBinary() });
db.Geos.Add(new Database.Geo() { Geography = geog2.AsBinary() });
db.SaveChanges();
}
Data retrieval is done like this:
using (var db = new Database.MyDbContext())
{
var geos = from g in db.Geos
select g.Geography;
}
The problem is that I get the following error: Invalid cast from 'System.String' to 'System.Byte[]'.
I also tried saving the geography data as a string. In this case I am able to retrieve it, but I can't create a DbGeography object from text.
Any ideas?
Thanks!
UPDATE:
The changes I made to ExpectedTypeConverter class:
else if (expectedType == typeof(byte[]))
{
if (currentType == typeof(Guid))
{
return ((Guid)value).ToByteArray();
}
else if (value is Array)
{
Array valueArray = (Array)value;
int byteLength = Buffer.ByteLength(valueArray);
byte[] bytes = new byte[byteLength];
Buffer.BlockCopy(valueArray, 0, bytes, 0, byteLength);
return bytes;
}
else if (currentType == typeof(System.String)) // this part was added
{
string hex = value.ToString();
int numChars = hex.Length / 2;
byte[] bytes = new byte[numChars];
using (var sr = new StringReader(hex))
{
for (int i = 0; i < numChars; i++)
{
bytes[i] = Convert.ToByte(new string(new char[2] { (char)sr.Read(), (char)sr.Read() }), 16);
}
}
return bytes;
}
else
{
// expect InvalidCastException from this call
return Convert.ChangeType(value, expectedType);
}

Parsing Through Wave File with BinaryReader

In .NET Assembly mscorlib System.IO namespace, I am using ReadInt16() method to loop through audio data bytes and dumping signed integer values into a text file. How does one interpret the two values associated with one sample rate? That is if I have one second of mono data there will be 88200 bytes, hence using ReadInt16() returns 88200 discrete integers. This is too much information, I should only have 44100 integers. So do I need to use a different method or perhaps advance the loop by 1 per each iteration.
Many thanks..........Mickey
using System;
using System.IO;
public struct WaveFormat
{
private short m_FormatTag; // most often PCM = 1
private short m_nChannels; // number of channels
private int m_SamplesPerSecond; // samples per second eg 44100
private int m_AvgBytesPerSecond; // bytes per second eg 176000
private short m_BlockAlign; // blockalign (byte per sample) eg 4 bytes
private short m_BitsPerSample; // bits per sample, 8, 16, 24
public WaveFormat(byte BPS, int SPS, byte nChn)
{
m_FormatTag = 1; //PCM
m_nChannels = nChn;
m_SamplesPerSecond = SPS;
m_BitsPerSample = BPS;
m_BlockAlign = (short)(m_nChannels * m_BitsPerSample / 8);
m_AvgBytesPerSecond = (int)(m_BlockAlign * m_SamplesPerSecond);
}
public short FormatTag
{
get { return m_FormatTag; }
set { m_FormatTag = value; }
}
public short Channels
{
get { return m_nChannels; }
}
public int SamplesPerSecond
{
get { return m_SamplesPerSecond; }
}
public int AvgBytesPerSecond
{
get { return m_AvgBytesPerSecond; }
}
public short BlockAlign
{
get { return m_BlockAlign; }
}
public short BitsPerSample
{
get { return m_BitsPerSample; }
}
public void Read(BinaryReader br)
{
m_FormatTag = br.ReadInt16();
m_nChannels = br.ReadInt16();
m_SamplesPerSecond = br.ReadInt32();
m_AvgBytesPerSecond = br.ReadInt32();
m_BlockAlign = br.ReadInt16();
m_BitsPerSample = br.ReadInt16();
}
public void Write(BinaryWriter bw)
{
bw.Write(m_FormatTag);
bw.Write(m_nChannels);
bw.Write(m_SamplesPerSecond);
bw.Write(m_AvgBytesPerSecond);
bw.Write(m_BlockAlign);
bw.Write(m_BitsPerSample);
}
public override string ToString()
{
System.Text.StringBuilder sb = new System.Text.StringBuilder();
sb.AppendLine("FormatTag: " + m_FormatTag.ToString());
sb.AppendLine("nChannels: " + m_nChannels.ToString());
sb.AppendLine("SamplesPerSecond: " + m_SamplesPerSecond.ToString());
sb.AppendLine("AvgBytesPerSecond: " + m_AvgBytesPerSecond.ToString());
sb.AppendLine("BlockAlign: " + m_BlockAlign.ToString());
sb.AppendLine("BitsPerSample: " + m_BitsPerSample.ToString());
return sb.ToString();
}
}
Generally when you read arrays of data your code should look like:
for(int i = 0; i < totalNumberOfEntries; i++)
{
// read all data for this entry
var component1 = reader.ReadXXX();
var component2 = reader.ReadXXX();
// deal with data for this entry
someEntryStroage.Add(new Entry(component1, component2);
}
Most likely (I don't know Wave file format) in your case you either need to read pairs of Int16 values (if samples are together) or read channels separately if data for one channel is after another.
you must read the chunkinfos. The data-chunk tells you how much bytes you have to read. the WaveFormat tells you ho much Averagebytespersecond you have, and much more. I have some VB-code...
have converted the VB-code with sharpdevelop to C# maybe it helps a little bit...
using System;
using System.IO;
public class ChunkInfo
{
private byte[] m_Header;
private long m_Length;
private long m_OffSet;
public ChunkInfo(string Header)
{
m_Header = new byte[Header.Length];
for (int i = 0; i <= m_Header.GetUpperBound(0); i++)
{
m_Header[i] = (byte)Header[i];
}
}
public ChunkInfo(byte[] Header)
{
m_Header = Header;
}
public void Read(BinaryReader br)
{
m_OffSet = SearchOffset(br);
if (m_OffSet >= 0)
{
br.BaseStream.Position = m_OffSet + m_Header.Length;
m_Length = br.ReadInt32();
}
}
public void Write(BinaryWriter bw)
{
bw.Write(m_Header);
bw.Write(m_Length);
}
public long Length
{
get { return m_Length; }
}
public long OffSet
{
get { return m_OffSet; }
}
private long SearchOffset(BinaryReader br)
{
byte[] haystack = null;
bool found = false;
long offset = 0;
long basepos = 0;
int hlength = 260;
long basepos_grow = hlength - m_Header.Length;
while (!(found || (basepos >= br.BaseStream.Length)))
{
br.BaseStream.Position = basepos;
haystack = br.ReadBytes(hlength);
offset = BoyerMooreHorspool.find(haystack, m_Header);
found = offset >= 0;
if (found)
{
offset += basepos;
break;
}
else
{
basepos += basepos_grow;
}
}
return offset;
}
}
public static class BoyerMooreHorspool
{
//detects a needle in the haystack
const int UBYTE_MAX = 255;
static int[] bad_char_skip4 = new int[UBYTE_MAX + 3];
static int[] bad_char_skip8 = new int[UBYTE_MAX + 3];
static bool IsInitialized = false;
public static void init()
{
//little optimization for needles with length 4 or 8
for (int i = 0; i <= UBYTE_MAX + 2; i++)
{
bad_char_skip4[i] = 4;
bad_char_skip8[i] = 8;
}
IsInitialized = true;
}
public static int find(byte[] haystack, byte[] needle, int start = 0)
{
if (!IsInitialized) init();
int i_n = 0;
//needle index
int n_n = needle.Length;
int[] bad_char_skip = null;
switch (n_n)
{
case 4:
bad_char_skip = bad_char_skip4;
break;
case 8:
bad_char_skip = bad_char_skip8;
break;
default:
bad_char_skip = new int[UBYTE_MAX + 3];
for (i_n = 0; i_n <= UBYTE_MAX + 2; i_n++)
{
bad_char_skip[i_n] = n_n;
}
break;
}
int ifind = -1;
//if not found then return - 1
int i_h = start;
//haystack index
int n_h = haystack.Length;
if (n_n > n_h)
throw new ArgumentOutOfRangeException("needle", "needle is to long");
int last = n_n - 1;
for (i_n = 0; i_n <= last - 1; i_n++)
{
bad_char_skip[needle[i_n]] = last - i_n;
}
byte bcs = 0;
int bhs = 0;
while ((n_h - start) >= n_n)
{
i_n = last;
while (haystack[i_h + i_n] == needle[i_n])
{
i_n -= 1;
if (i_n == 0)
{
ifind = i_h;
break;
}
}
bhs = haystack[i_h + last];
bcs = (byte)(bad_char_skip[bhs]);
n_h -= bcs;
i_h += bcs;
}
return ifind;
}
}

Why is my StringReader 50us slower than the .NET StringReader?

In my test I created a string with 32000 characters.
After repeated execution of the test the BCL StringReader consistently executed in 350us while mine ran in 400us. What kind of secrets are they hiding?
Test:
private void SpeedTest()
{
String r = "";
for (int i = 0; i < 1000; i++)
{
r += Randomization.GenerateString();
}
StopWatch s = new StopWatch();
s.Start();
using (var sr = new System.IO.StringReader(r))
{
while (sr.Peek() > -1)
{
sr.Read();
}
}
s.Stop();
_Write(s.Elapsed);
s.Reset();
s.Start();
using (var sr = new MagicSynthesis.StringReader(r))
{
while (sr.PeekNext() > Char.MinValue)
{
sr.Next();
}
}
s.Stop();
_Write(s.Elapsed);
}
Code:
public unsafe class StringReader : IDisposable
{
private Char* Base;
private Char* End;
private Char* Current;
private const Char Null = '\0';
/// <summary></summary>
public StringReader(String s)
{
if (s == null)
throw new ArgumentNullException("s");
Base = (Char*)Marshal.StringToHGlobalUni(s).ToPointer();
End = (Base + s.Length);
Current = Base;
}
/// <summary></summary>
public Char Next()
{
return (Current < End) ? *(Current++) : Null;
}
/// <summary></summary>
public String Next(Int32 length)
{
String s = String.Empty;
while (Current < End && length > 0)
{
length--;
s += *(Current++);
}
return s;
}
/// <summary></summary>
public Char PeekNext()
{
return *(Current);
}
/// <summary></summary>
public String PeekNext(Int32 length)
{
String s = String.Empty;
Char* a = Current;
while (Current < End && length > 0)
{
length--;
s += *(Current++);
}
Current = a;
return s;
}
/// <summary></summary>
public Char Previous()
{
return ((Current > Base) ? *(--Current) : Null);
}
/// <summary></summary>
public Char PeekPrevious()
{
return ((Current > Base) ? *(Current - 1) : Null);
}
/// <summary></summary>
public void Dispose()
{
Marshal.FreeHGlobal(new IntPtr(Base));
}
}
I would bet that Marshal.StringToHGlobalUni() and Marshal.FreeHGlobal(new IntPtr(Base)) have a lot to do with the differences. I'm not sure how StringReader manages the string, but I bet it's not copying it to unmanaged memory.
Looking at the StringReader.Read() method in Reflector shows this:
public override int Read()
{
if (this._s == null)
{
__Error.ReaderClosed();
}
if (this._pos == this._length)
{
return -1;
}
return this._s[this._pos++];
}
The contructor is also just:
public StringReader(string s)
{
if (s == null)
{
throw new ArgumentNullException("s");
}
this._s = s;
this._length = (s == null) ? 0 : s.Length;
}
So, it appear that StringReader just maintains the current position and uses regular indexes to return values.
Edit
In response to your comment, your Next() method does a comparison and an unsafe cast, which probably isn't optimized in any way. StringReader.Read() does simple comparison and returns the character as _pos index in the string, which probably has some optimization by the compiler.
Maybe Reflector would help you find your answer?
You can always look at the source code
Couldn't tell after simply looking at your code, but here's the code for StringReader.Read():
public override int Read()
{
if (this._s == null)
{
__Error.ReaderClosed();
}
if (this._pos == this._length)
{
return -1;
}
return this._s[this._pos++];
}
They've got two simple value checks and an array access plus increment, versus your value check and pointer increment. Perhaps it would be useful to look at the IL and see how many ops each compiles down to.
Have you tried profiling your StringReader to see if there are any obvious places where you could save time? This is the most reliable way to determine what the bottlenecks in your code are.
Normally I would suggest profiling your solution against the other but I'm not sure about the viability of profiling the BCL. It's GAC'd and strongly signed which makes instrumentation difficult so you would have to rely on sampling.

Ideas to manage IP and Ports in Key/Value format?

I'm looking for a good and fast way to manage IP addresses and ports in a file. Sort of a DB Table that has 2 columns: IP and Port, but in a file, without using a DB.
It has to support adding, deleting and updating. I don't care from concurrency.
Below, some come to complete your task. I tried to go strictly to the point, so maybe something is missing.
I'd to create a "Record" class, to keep ip/port pairs
class Record : IPEndPoint, IComparable<Record>
{
internal long Offset { get; set; }
public bool Deleted { get; internal set; }
public Record() : base(0, 0)
{
Offset = -1;
Deleted = false;
}
public int CompareTo(Record other)
{
if (this.Address == other.Address && this.Address == other.Address )
return 0;
else if (this.Address == other.Address)
return this.Port.CompareTo(other.Port);
else
return
BitConverter.ToInt32(this.Address.GetAddressBytes(), 0).CompareTo(
BitConverter.ToInt32(other.Address.GetAddressBytes(), 0));
}
}
class RecordComparer : IComparer<Record>
{
public int Compare(Record x, Record y)
{
return x.CompareTo(y);
}
}
...And a "DatabaseFile" class to manage datafile interaction.
class DatabaseFile : IDisposable
{
private FileStream file;
private static int RecordSize = 7;
private static byte[] Deleted = new byte[] { 42 };
private static byte[] Undeleted = new byte[] { 32 };
public DatabaseFile(string filename)
{
file = new FileStream(filename,
FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
}
public IEnumerable<Record> Locate(Predicate<Record> record)
{
file.Seek(0, SeekOrigin.Begin);
while (file.Position < file.Length)
{
long offset = file.Position;
byte[] buffer = new byte[DatabaseFile.RecordSize];
file.Read(buffer, 0, DatabaseFile.RecordSize);
Record current = Build(offset, buffer);
if (record.Invoke(current))
yield return current;
}
}
public void Append(Record record)
{
// should I look for duplicated values? i dunno
file.Seek(0, SeekOrigin.End);
record.Deleted = false;
record.Offset = file.Position;
Write(record);
}
public void Delete(Record record)
{
if (record.Offset == -1) return;
file.Seek(record.Offset, SeekOrigin.Begin);
record.Deleted = true;
Write(record);
}
public void Update(Record record)
{
if (record.Offset == -1)
{
Append(record);
}
else
{
file.Seek(record.Offset, SeekOrigin.Begin);
Write(record);
}
}
private void Write(Record record)
{
file.Write(GetBytes(record), 0, DatabaseFile.RecordSize);
}
private Record Build(long offset, byte[] data)
{
byte[] ipAddress = new byte[4];
Array.Copy(data, 1, ipAddress, 0, ipAddress.Length);
return new Record
{
Offset = offset,
Deleted = (data[0] == DatabaseFile.Deleted[0]),
Address = new IPAddress(ipAddress),
Port = BitConverter.ToInt16(data, 5)
};
}
private byte[] GetBytes(Record record)
{
byte[] returnValue = new byte[DatabaseFile.RecordSize];
Array.Copy(
record.Deleted ? DatabaseFile.Deleted : DatabaseFile.Undeleted, 0,
returnValue, 0, 1);
Array.Copy(record.Address.GetAddressBytes(), 0,
returnValue, 1, 4);
Array.Copy(BitConverter.GetBytes(record.Port), 0,
returnValue, 5, 2);
return returnValue;
}
public void Pack()
{
long freeBytes = 0;
byte[] buffer = new byte[RecordSize];
Queue<long> deletes = new Queue<long>();
file.Seek(0, SeekOrigin.Begin);
while (file.Position < file.Length)
{
long offset = file.Position;
file.Read(buffer, 0, RecordSize);
if (buffer[0] == Deleted[0])
{
deletes.Enqueue(offset);
freeBytes += RecordSize;
}
else
{
if (deletes.Count > 0)
{
deletes.Enqueue(offset);
file.Seek(deletes.Dequeue(), SeekOrigin.Begin);
file.Write(buffer, 0, RecordSize);
file.Seek(offset + RecordSize, SeekOrigin.Begin);
}
}
}
file.SetLength(file.Length - freeBytes);
}
public void Sort()
{
int offset = -RecordSize; // lazy method
List<Record> records = this.Locate(r => true).ToList();
records.Sort(new RecordComparer());
foreach (Record record in records)
{
record.Offset = offset += RecordSize;
Update(record);
}
}
public void Dispose()
{
if (file != null)
file.Close();
}
}
Below, a working example:
static void Main(string[] args)
{
List<IPEndPoint> endPoints = new List<IPEndPoint>(
new IPEndPoint[]{
new IPEndPoint(IPAddress.Parse("127.0.0.1"), 80),
new IPEndPoint(IPAddress.Parse("69.59.196.211"), 80),
new IPEndPoint(IPAddress.Parse("74.125.45.100"), 80)
});
using (DatabaseFile dbf = new DatabaseFile("iptable.txt"))
{
foreach (IPEndPoint endPoint in endPoints)
dbf.Append(new Record {
Address = endPoint.Address,
Port = endPoint.Port });
Record stackOverflow = dbf.Locate(r =>
Dns.GetHostEntry(r.Address)
.HostName.Equals("stackoverflow.com")).FirstOrDefault();
if (stackOverflow != null)
dbf.Delete(stackOverflow);
Record google = dbf.Locate(r =>
r.Address.ToString() == "74.125.45.100").First();
google.Port = 443;
dbf.Update(google);
foreach(Record http in dbf.Locate(r =>
!r.Deleted && r.Port == 80))
Console.WriteLine(http.ToString());
}
Console.ReadLine();
}
dBase III, I miss you.
Well, that was fun, thank you!
EDIT 1: Added Pack() and lazy Sort() code;
EDIT 2: Added missing IComparable/IComparer implementation
I personally will go for
192.100.10.1:500:20-21
192.100.10.2:27015-27016:80
Where the first is the Ip and every thing after the : is a port, We can also represent a range by - and if we want to be very crazy about it we can introduce a u which will represent the port type UDP or TCP for example:
192.100.10.2:27015-27016:80:90u
And explode() would work for the above quite easily.
When talking about Inserting Deleting and updating.. We can simply create a class structure such as
struct port{
int portnum;
char type;
port(int portnum = 0, char type = 't'){
this.portnum = portnum; this.type = type;
}
}
class Ip{
public:
string Ip_str;
list <port> prt;
}
And then you can have the main to look like
int main(){
list<Ip> Ips;
//Read the list from file and update the list.
//Sort delete update the list
//Rewrite the list back into file in the order mentioned obove
return 0;
}
The easiest way is probably to create a small class that contains your IP and port
class IpAddress
{
public string IP;
public int port;
}
and then create a list<IpAddress> of them. You can then use XML Serialization and Deserialization to read to and write from a file your list.
The .NET BCL does not offer what you are looking for as you want to query against a file without loading it into memory first and support add/remove. So you'd either have to roll your own embedded database or you could simply use something like SQLite http://www.sqlite.org/
IP and Port is a one to many relationship. I would consider something like this
\t192.168.1.1\r\n25\r\n26\r\n\t192.168.1.2\r\n2\r\n80\r\n110
where \t is a tab and \r\n is a carriage return followed by a newline
So when you parse, if you hit a tab character, you know everything that's in that line from there to the newline is an IP address, then everything in between the next newlines is a port number for that IP address until you hit a tab, in which case you're on a new IP address. That's simple and fast but not as human readable.
this has nothing to do with IP and ports.. the problem is that, as far as i know, windows does not allow to INSERT or remove bytes in/from the middle of a file..

Categories

Resources