Giter Site home page Giter Site logo

picosha2's Introduction

PicoSHA2 - a C++ SHA256 hash generator

Copyright © 2017 okdshin

Introduction

PicoSHA2 is a tiny SHA256 hash generator for C++ with following properties:

  • header-file only
  • no external dependencies (only uses standard C++ libraries)
  • STL-friendly
  • licensed under MIT License

Generating SHA256 hash and hash hex string

// any STL sequantial container (vector, list, dequeue...)
std::string src_str = "The quick brown fox jumps over the lazy dog";

std::vector<unsigned char> hash(picosha2::k_digest_size);
picosha2::hash256(src_str.begin(), src_str.end(), hash.begin(), hash.end());

std::string hex_str = picosha2::bytes_to_hex_string(hash.begin(), hash.end());

Generating SHA256 hash and hash hex string from byte stream

picosha2::hash256_one_by_one hasher;
...
hasher.process(block.begin(), block.end());
...
hasher.finish();

std::vector<unsigned char> hash(picosha2::k_digest_size);
hasher.get_hash_bytes(hash.begin(), hash.end());

std::string hex_str = picosha2::get_hash_hex_string(hasher);

The file example/interactive_hasher.cpp has more detailed information.

Generating SHA256 hash from a binary file

std::ifstream f("file.txt", std::ios::binary);
std::vector<unsigned char> s(picosha2::k_digest_size);
picosha2::hash256(f, s.begin(), s.end());

This hash256 may use less memory than reading whole of the file.

Generating SHA256 hash hex string from std::string

std::string src_str = "The quick brown fox jumps over the lazy dog";
std::string hash_hex_str;
picosha2::hash256_hex_string(src_str, hash_hex_str);
std::cout << hash_hex_str << std::endl;
//this output is "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"
std::string src_str = "The quick brown fox jumps over the lazy dog";
std::string hash_hex_str = picosha2::hash256_hex_string(src_str);
std::cout << hash_hex_str << std::endl;
//this output is "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592"
std::string src_str = "The quick brown fox jumps over the lazy dog.";//add '.'
std::string hash_hex_str = picosha2::hash256_hex_string(src_str.begin(), src_str.end());
std::cout << hash_hex_str << std::endl;
//this output is "ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c"

Generating SHA256 hash hex string from byte sequence

std::vector<unsigned char> src_vect(...);
std::string hash_hex_str;
picosha2::hash256_hex_string(src_vect, hash_hex_str);
std::vector<unsigned char> src_vect(...);
std::string hash_hex_str = picosha2::hash256_hex_string(src_vect);
unsigned char src_c_array[picosha2::k_digest_size] = {...};
std::string hash_hex_str;
picosha2::hash256_hex_string(src_c_array, src_c_array+picosha2::k_digest_size, hash_hex_str);
unsigned char src_c_array[picosha2::k_digest_size] = {...};
std::string hash_hex_str = picosha2::hash256_hex_string(src_c_array, src_c_array+picosha2::k_digest_size);

Generating SHA256 hash byte sequence from STL sequential container

//any STL sequantial container (vector, list, dequeue...)
std::string src_str = "The quick brown fox jumps over the lazy dog";

//any STL sequantial containers (vector, list, dequeue...)
std::vector<unsigned char> hash(picosha2::k_digest_size);

// in: container, out: container
picosha2::hash256(src_str, hash);
//any STL sequantial container (vector, list, dequeue...)
std::string src_str = "The quick brown fox jumps over the lazy dog";

//any STL sequantial containers (vector, list, dequeue...)
std::vector<unsigned char> hash(picosha2::k_digest_size);

// in: iterator pair, out: contaner
picosha2::hash256(src_str.begin(), src_str.end(), hash);
std::string src_str = "The quick brown fox jumps over the lazy dog";
unsigned char hash_byte_c_array[picosha2::k_digest_size];
// in: container, out: iterator(pointer) pair
picosha2::hash256(src_str, hash_byte_c_array, hash_byte_c_array+picosha2::k_digest_size);
std::string src_str = "The quick brown fox jumps over the lazy dog";
std::vector<unsigned char> hash(picosha2::k_digest_size);
// in: iterator pair, out: iterator pair
picosha2::hash256(src_str.begin(), src_str.end(), hash.begin(), hash.end());

picosha2's People

Contributors

corporal9736 avatar eklitzke avatar fireuse avatar forrestcavalier avatar okdshin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

picosha2's Issues

unexpected behavior, picosha2 cuts off leading/trailing spaces

picosha2 seems to hash " one" and "one" and "one " as the same.

as well as "on e" and "on ( any number of more spaces here) e"

These should be different though, especially when hashing. How does one "make spaces count," for your implementation of SHA256?

Release tags

Firstly, thanks for your great work, @okdshin!

We’ve started using PicoSHA2 in our product. In production, versioning and tracking changes are important. So stable release tags such as v1.0.0, will be very useful because master can be unstable.

References

Compilation warnings with Clang

A whole bunch of warnings pop up when compiling with Clang.

Can we fix these please?

picosha2.h:199:57: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
            detail::hash256_block(h_, buffer_.begin() + i,
                                                      ~ ^
picosha2.h:200:53: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
                                  buffer_.begin() + i + 64);
                                                  ~ ^
picosha2.h:202:58: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
        buffer_.erase(buffer_.begin(), buffer_.begin() + i);
                                                       ~ ^
picosha2.h:301:32: error: implicit conversion changes signedness: 'int' to 'std::__1::vector<unsigned char, std::__1::allocator<unsigned char> >::size_type' (aka 'unsigned int') [-Werror,-Wsign-conversion]
    std::vector<byte_t> buffer(buffer_size);
                        ~~~~~~ ^~~~~~~~~~~
picosha2.h:199:57: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
            detail::hash256_block(h_, buffer_.begin() + i,
                                                      ~ ^
picosha2.h:293:12: note: in instantiation of function template specialization 'picosha2::hash256_one_by_one::process<std::__1::__wrap_iter<const char *> >' requested here
    hasher.process(first, last);
           ^
picosha2.h:323:21: note: in instantiation of function template specialization 'picosha2::impl::hash256_impl<std::__1::__wrap_iter<const char *>, unsigned char *>' requested here
    picosha2::impl::hash256_impl(
                    ^
picosha2.h:346:5: note: in instantiation of function template specialization 'picosha2::hash256<std::__1::__wrap_iter<const char *>, unsigned char *>' requested here
    hash256(first, last, hashed, hashed + k_digest_size);
    ^
picosha2.h:360:5: note: in instantiation of function template specialization 'picosha2::hash256_hex_string<std::__1::__wrap_iter<const char *> >' requested here
    hash256_hex_string(src.begin(), src.end(), hex_str);
    ^
picosha2.h:200:53: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
                                  buffer_.begin() + i + 64);
                                                  ~ ^
picosha2.h:202:58: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
        buffer_.erase(buffer_.begin(), buffer_.begin() + i);
                                                       ~ ^
picosha2.h:98:59: error: implicit conversion changes signedness: 'unsigned int' to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
        w[i] = (static_cast<word_t>(mask_8bit(*(first + i * 4))) << 24) |
                                                      ~ ~~^~~
picosha2.h:199:21: note: in instantiation of function template specialization 'picosha2::detail::hash256_block<unsigned long *, std::__1::__wrap_iter<unsigned char *> >' requested here
            detail::hash256_block(h_, buffer_.begin() + i,
                    ^
picosha2.h:293:12: note: in instantiation of function template specialization 'picosha2::hash256_one_by_one::process<std::__1::__wrap_iter<const char *> >' requested here
    hasher.process(first, last);
           ^
picosha2.h:323:21: note: in instantiation of function template specialization 'picosha2::impl::hash256_impl<std::__1::__wrap_iter<const char *>, unsigned char *>' requested here
    picosha2::impl::hash256_impl(
                    ^
picosha2.h:346:5: note: in instantiation of function template specialization 'picosha2::hash256<std::__1::__wrap_iter<const char *>, unsigned char *>' requested here
    hash256(first, last, hashed, hashed + k_digest_size);
    ^
picosha2.h:360:5: note: in instantiation of function template specialization 'picosha2::hash256_hex_string<std::__1::__wrap_iter<const char *> >' requested here
    hash256_hex_string(src.begin(), src.end(), hex_str);
    ^
picosha2.h:99:59: error: implicit conversion changes signedness: 'unsigned int' to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
               (static_cast<word_t>(mask_8bit(*(first + i * 4 + 1))) << 16) |
                                                      ~ ~~^~~
picosha2.h:100:59: error: implicit conversion changes signedness: 'unsigned int' to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
               (static_cast<word_t>(mask_8bit(*(first + i * 4 + 2))) << 8) |
                                                      ~ ~~^~~
picosha2.h:101:59: error: implicit conversion changes signedness: 'unsigned int' to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
               (static_cast<word_t>(mask_8bit(*(first + i * 4 + 3))));
                                                      ~ ~~^~~
picosha2.h:199:57: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
            detail::hash256_block(h_, buffer_.begin() + i,
                                                      ~ ^
picosha2.h:293:12: note: in instantiation of function template specialization 'picosha2::hash256_one_by_one::process<std::__1::__wrap_iter<unsigned char *> >' requested here
    hasher.process(first, last);
           ^
picosha2.h:323:21: note: in instantiation of function template specialization 'picosha2::impl::hash256_impl<std::__1::__wrap_iter<unsigned char *>, std::__1::__wrap_iter<unsigned char *> >' requested here
    picosha2::impl::hash256_impl(
                    ^
blah.h:78:13: note: in instantiation of function template specialization 'picosha2::hash256<std::__1::__wrap_iter<unsigned char *>, std::__1::__wrap_iter<unsigned char *> >' requested here
  picosha2::hash256(image.begin(), image.end(), hash.begin(), hash.end());
            ^
picosha2.h:200:53: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
                                  buffer_.begin() + i + 64);
                                                  ~ ^
picosha2.h:202:58: error: implicit conversion changes signedness: 'std::size_t' (aka 'unsigned int') to 'std::__1::__wrap_iter<unsigned char *>::difference_type' (aka 'int') [-Werror,-Wsign-conversion]
        buffer_.erase(buffer_.begin(), buffer_.begin() + i);
                                                       ~ ^

warning C4244: 'argument': conversion from '__int64' to 'picosha2::word_t', possible loss of data

Under msvc 2017 on x86_64, I get the following warning:

warning C4244: 'argument': conversion from '__int64' to 'picosha2::word_t', possible loss of data
picosha2.h(291): note: see reference to function template instantiation 'void picosha2::hash256_one_by_one::process<RaIter>(RaIter,RaIter)' being compiled
          with
          [
              RaIter=std::_String_const_iterator<std::_String_val<std::_Simple_types<char>>>
          ]
  picosha2.h(323): note: see reference to function template instantiation 'void picosha2::impl::hash256_impl<InIter,OutIter>(RaIter,RaIter,OutIter,OutIter,int,std::random_access_iterator_tag)' being compiled
          with
          [
              InIter=std::_String_const_iterator<std::_String_val<std::_Simple_types<char>>>,
              OutIter=picosha2::byte_t *,
              RaIter=std::_String_const_iterator<std::_String_val<std::_Simple_types<char>>>
          ]
  picosha2.h(344): note: see reference to function template instantiation 'void picosha2::hash256<InIter,picosha2::byte_t*>(InIter,InIter,OutIter,OutIter,int)' being compiled
          with
          [
              InIter=std::_String_const_iterator<std::_String_val<std::_Simple_types<char>>>,
              OutIter=picosha2::byte_t *
          ]
  picosha2.h(358): note: see reference to function template instantiation 'void picosha2::hash256_hex_string<std::_String_const_iterator<std::_String_val<std::_Simple_types<_Ty>>>>(InIter,InIter,std::string &)' being compiled
          with
          [
              _Ty=char,
              InIter=std::_String_const_iterator<std::_String_val<std::_Simple_types<char>>>
          ]

The same warning doesn't happen on x86. I also don't see any such warnings on gcc.

Speed

I am trying to incorporate this into my project, I am using a one by one hasher with a large amount of binary data. It is very slow and I was wondering if I was doing something wrong.

Generates wrong value

src : "The quick brown fox jumps over the lazy dog"
hash: 4e61a8ed1a377d2a9a1f2287138d46472be31bdba9d7d679d1d8b7e1e4cb6413

Legit syntax error

Yeah, so I downloaded your library and everything. It is working really well and with great performance but you know. There is quite a huge flaw. I mean what the fuck were you thinking. Who even puts curly braces on the same line as the statement, function whatever. Like are you serious. I will call the cyber swat on you.

Hash for a file

Currently there seems to be no interface suitable for hash computation of big files (small files can be read in a string and then fed to the hash computation).

The following does not work because std::distance (here) already consumes the istream iterators (SO-link).

std::ifstream fileStream("file.avi");
std::cout << picosha2::hash256_hex_string(std::istream_iterator<unsigned char>(fileStream),
              std::istream_iterator<unsigned char>()));

Hash calculates differently when reading from binary file

Here is my rough code:

// Write to file
// Works ok

uint64_t me = 76561198040894045;

std::string str_to_hash = "test";
std::vector<unsigned char> s(picosha2::k_digest_size);
picosha2::hash256(str_to_hash.begin(), str_to_hash.end(), s);

std::ofstream odt;
odt.open("test.txt", std::ios::out | std::ios::binary);

// Write uint64_t using two uint32_t
uint32_t hval = htonl((me >> 32) & 0xFFFFFFFF);
uint32_t lval = htonl(me & 0xFFFFFFFF);
odt.write((const char*)&hval, sizeof(hval));
odt.write((const char*)&lval, sizeof(lval));

for (unsigned int i = 0; i < s.size(); i++) {
	odt.write((const char*)&s.at(i), sizeof(s.at(i)));
}

odt.flush();
odt.close();
// Read from file
// Doesn't work

std::ifstream idt("test.txt", std::ios::in | std::ios::binary);
if (idt.is_open() && !idt.fail()) {
	uint64_t steamid64;
	while (idt) {
		uint32_t val[2] = { 0 };
		if (idt.read((char*)val, sizeof(val)))
		{
			steamid64 = (uint64_t)ntohl(val[0]) << 32 | (uint64_t)ntohl(val[1]);
		}

		std::vector<unsigned char> s(picosha2::k_digest_size);
		picosha2::hash256(idt, s.begin(), s.end());

		if (idt.eof()) break;

		std::string hash_hex_str;
		picosha2::bytes_to_hex_string(s.begin(), s.end(), hash_hex_str);

		std::cout << hash_hex_str;
	}

	idt.close();
}

Output:

954d5a49fd70d9b8bcdb35d252267829957f7ef7fa6c74f88419bdc5e82209f4

test SHA256:

9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08


I can get it to work by reading manually into the vector:

// Read from file
// Works ok

std::ifstream idt("test.txt", std::ios::in | std::ios::binary);
if (idt.is_open() && !idt.fail()) {
	uint64_t steamid64;
	while (idt) {
		uint32_t val[2] = { 0 };
		if (idt.read((char*)val, sizeof(val)))
		{
			steamid64 = (uint64_t)ntohl(val[0]) << 32 | (uint64_t)ntohl(val[1]);
		}

		std::vector<unsigned char> s(picosha2::k_digest_size);
		for (unsigned int i = 0; i < 32; i++) {
			uint8_t blah;
			idt.read((char*)&blah, sizeof(blah));
			s.at(i) = blah;
		}
		//picosha2::hash256(idt, s.begin(), s.end());

		if (idt.eof()) break;

		std::string hash_hex_str;
		picosha2::bytes_to_hex_string(s.begin(), s.end(), hash_hex_str);

		std::cout << hash_hex_str;
	}

	idt.close();
}

Output:

9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08

different hashes computed

Running this program

#include "picosha2.h"

#include <fstream>
#include <iostream>
#include <cstring>


std::string hash1(std::string fileName)
{
    std::ifstream inputFile(fileName, std::ios::binary);
    std::vector<char> hashVec(picosha2::k_digest_size);
    std::string hexHash;    

    picosha2::hash256(inputFile, hashVec.begin(), hashVec.end());
    picosha2::hash256_hex_string(hashVec, hexHash);
    inputFile.close();
    
    return hexHash;
}

std::string hash2(std::string fileName)
{
    std::ifstream inputFile(fileName, std::ios::binary);
    std::istreambuf_iterator<char> start(inputFile);
    std::istreambuf_iterator<char> end;
    std::vector<char> data(start, end);
    std::vector<char> hashVec(picosha2::k_digest_size);
    std::string hexHash;    
    
    picosha2::hash256(data.begin(), data.end(), hashVec);
    picosha2::hash256_hex_string(hashVec, hexHash);
    inputFile.close();
    
    return hexHash;
}

std::string hash3(std::string fileName)
{
    picosha2::hash256_one_by_one hasher;
    std::ifstream inputFile(fileName, std::ios::binary);
    std::vector<char> dataToHash;
    std::istreambuf_iterator<char> it(inputFile);
    std::istreambuf_iterator<char> endIt;
    for (; it != endIt; ++it) 
        dataToHash.push_back(*it);

    
    hasher.process(dataToHash.begin(), 
                   dataToHash.end());
    hasher.finish();
    
    std::string hexHash;
    picosha2::get_hash_hex_string(hasher, hexHash);
    inputFile.close();
    
    return hexHash;
}

std::string hash4(std::string fileName)
{
    picosha2::hash256_one_by_one hasher;
    std::ifstream inputFile(fileName, std::ios::binary);

    hasher.process(std::istreambuf_iterator<char>(inputFile),
                   std::istreambuf_iterator<char>());

    hasher.finish();
    
    std::string hexHash;
    picosha2::get_hash_hex_string(hasher, hexHash);
    inputFile.close();
    
    
    return hexHash;
}

std::string hash5()
{
    char text[] = "The quick brown fox jumps over the lazy dog";
    std::vector<char> hashVec(picosha2::k_digest_size);
    std::string hexHash;    

    picosha2::hash256(&text[0], &text[strlen(text)], hashVec);
    picosha2::hash256_hex_string(hashVec, hexHash);
    
    return hexHash;    
}

std::string hash6()
{
    // Example from README.md:
    std::string src_str = "The quick brown fox jumps over the lazy dog";
    std::string hash_hex_str;
    picosha2::hash256_hex_string(src_str, hash_hex_str);
    
    return hash_hex_str;
}

int main(int, char**)
{
    std::string filename("test.txt");
    
    std::cout << "hash1: " << hash1(filename) << std::endl;
    std::cout << "hash2: " << hash2(filename) << std::endl;
    std::cout << "hash3: " << hash3(filename) << std::endl;
    std::cout << "hash4: " << hash4(filename) << std::endl;
    std::cout << "hash5: " << hash5() << std::endl;
    std::cout << "hash6: " << hash6() << std::endl;

    return 0;
}

on a file that contains "The quick brown fox jumps over the lazy dog" (without newline!) results in a number of different Hashes:

g++ -std=c++11 test_hashes.cpp -o test_hashes && ./test_hashes

hash1: 6d37795021e544d82b41850edf7aabab9a0ebe274e54a519840c4666f35b3937
hash2: 6d37795021e544d82b41850edf7aabab9a0ebe274e54a519840c4666f35b3937
hash3: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592
hash4: dc28302600b7b0f5f2c8f20e3e5ca99b705337d6712640d607eff1237d0b8c72
hash5: 6d37795021e544d82b41850edf7aabab9a0ebe274e54a519840c4666f35b3937
hash6: d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592

Add test vectors and performance test

I wonder if you guys are going to add test vectors and performance test scripts for this header file in the future?
Also a GH workflow would be nice

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.