Giter VIP home page Giter VIP logo

enoengine's Introduction

EnoEngine Build Status

This is the engine powering our CTFs.

For performance reasons, it's written in C#.

Usage

(0. Make sure the dependencies are installed: docker, docker-compose, dotnet sdk ...)

  1. Create a ctf.json (see below)
  2. Make sure the data folder exists (./../data/)
  3. Start up the Database (docker-compose up -d) (or run tmux.sh)
  4. Run EnoConfig to apply the configuration (dotnet run --project EnoConfig apply)
  5. Run EnoLauncher (dotnet run -c Release --project EnoLauncher)
  6. Run EnoFlagSink (dotnet run -c Release --project EnoFlagSink)
  7. Once you want to start the CTF (i.e. distribute flags): run EnoEngine (dotnet run -c Release --project EnoEngine)

ctf.json Format

interface ctfjson {
    title: string;
    flagValidityInRounds: number;
    checkedRoundsPerRound: number;
    roundLengthInSeconds: number;
    dnsSuffix: string;
    teamSubnetBytesLength: number;
    flagSigningKey: string;
    encoding: string | null;
    services: Service[];
    teams: Team[];
}
interface Service {
    id: number;
    name: string;
    flagsPerRoundMultiplier: number;
    noisesPerRoundMultiplier: number;
    havocsPerRoundMultiplier: number;
    weightFactor: number;
    active: string | null;
    checkers: string[];
}

interface Team {
    id: number;
    name: string;
    address: string | null;
    teamSubnet: string;
    logoUrl: string | null;
    countryCode: string | null;
    active: string | null;
}

Development

  1. Install the dotnet sdk-5. Download
  2. Use any IDE you like (Visual Studio or VSCode recommended)
  3. If your IDE doesn't do it automatically, run dotnet restore

Database

For creating a migration after changes, run this:

cd ./EnoDatabase
rm -r Migrations
dotnet ef migrations add InitialMigrations --startup-project ../EnoEngine

Checker API v2

Checkers are expected to respond to these requests, providing a HTTP Status Code 200:

GET /service

Response:

interface CheckerInfoMessage {
    serviceName: string;                        // Name of the service
    flagVariants: number;                       // Number of different variants supported for storing/retrieving flags. Each variant must correspond to a different location/flag store in the service.
    noiseVariants: number;                      // Number of different variants supported for storing/retrieving noise. Different variants must not necessarily store the noise in different locations.
    havocVariants: number;                      // Number of different variants supported for havoc.
}

POST /

Parameter:

interface CheckerTaskMessage {
    taskId: number;                             // The per-ctf unique id of a task.
    method: "putflag" | "getflag" | "putnoise" | "getnoise" | "havoc";
    address: string;                            // The address of the target team's vulnbox. Can be either an IP address or a valid hostname.
    teamId: number;                             // The id of the target team.
    teamName: string;                           // The name of the target team.
    currentRoundId: number;                     // The id of the current round.
    relatedRoundId: number;                     // For "getflag" and "getnoise", this is the id of the round in which the corresponding "putflag" or "putnoise" happened. For "putflag", "putnoise" and "havoc", this is always identical to currentRoundId. Use the taskChainId to store/retrieve data related to the corresponding "putflag" or "putnoise" instead of using relatedRoundId directly.
    flag: string | null;                        // The flag for putflag and getflag, otherwise null.
    variantId: number;                          // The variant id of the task. Used to support different flag, noise and havoc methods. Starts at 0.
    timeout: number;                            // Timeout for the task in milliseconds.
    roundLength: number;                        // Round length in milliseconds.
    taskChainId: string;                        // The unique identifier of a chain of tasks (i.e. putflag and getflags or putnoise and getnoise for the same flag/noise share an Id, each havoc has its own Id). Should be used in the database to store e.g. credentials created during putlfag and required in getflag. It is up to the caller to ensure the aforementioned criteria are met, the Engine achieves this by composing it the following way: "{flag|noise|havoc}_s{serviceId}_r{relatedRoundId}_t{teamId}_i{uniqueVariantIndex}". A checker may be called multiple times with the same method, serviceId, roundId, teamId and variantId, in which case the uniqueVariantIndex can be used to distinguish the taskChains.
}

Response:

interface CheckerResultMessage {
    result: string;                             // "INTERNAL_ERROR", "OK", MUMBLE", or "OFFLINE".
    message: string | null;                     // message describing the error, displayed on the public scoreboard if not null
}

Scoreboard API

interface Scoreboard {
    currentRoundId: number | null;
    startTimestamp: string | null;              // Start timestamp of the current round according to ISO-86-01 ("yyyy-MM-ddTHH:mm:ss.fffZ") in UTC.
    endTimestamp: string | null;                // End timestamp of the current round according to ISO-86-01 ("yyyy-MM-ddTHH:mm:ss.fffZ") in UTC.
    dnsSuffix: string | null;                   // The DNS suffix (including the leading dot), if DNS is used. Example: ".bambi.ovh"
    services: ScoreboardService[];
    teams: ScoreboardTeam[];
}

interface ScoreboardTeam {
    teamName: string;                           // The name of the team.
    teamId: number;                             // The id of the team.
    logoUrl: string | null;                     // An URL with the team's logo, or null.
    countryCode: string | null;                 // The ISO 3166-1 alpha-2 country code (uppercase), or null.
    totalScore: number;                         // The total Score of the team.
    attackScore: number;                        // The attack Score of the team.
    defenseScore: number;                       // The defense Score of the team.
    serviceLevelAgreementScore: number;         // The SLA Score of the team.
    serviceDetails: ScoreboardTeamServiceDetails[];
}

interface ScoreboardTeamServiceDetails {
    serviceId: number;                          // The id of the service.
    attackScore: number;                        // The attack Score of the team in the service.
    defenseScore: number;                       // The defense Score of the team.
    serviceLevelAgreementScore: number;         // The SLA Score of the team in the service.
    serviceStatus: string;                      // "INTERNAL_ERROR", "OFFLINE", "MUMBLE", "RECOVERING", "OK", "INACTIVE"
    message: string | null;                     // Leave null for no message, otherwise the message is displayed
}

interface ScoreboardService {
    serviceId: number;                          // The id of the service.
    serviceName: string;                        // The name of the service.
    flagVariants: number;                       // The amount of different flag variants.
    firstBloods: FirstBlood[];
}

interface FirstBlood {
    teamId: number;                             // The id of the team that scored the firstblood.
    teamName: number;                           // The name of the team that scored the firstblood.
    timestamp: string;                          // Timestamp according to ISO-86-01 ("yyyy-MM-ddTHH:mm:ss.fffZ") in UTC.
    roundId: number;                            // The id of the round in which the firstblood was submitted.
    flagVariantId: number;                      // The id of the variant.
}

Flagsubmission Endpoint:

The Flagsubmission is done via a tcp connection to port 1337. There you can just send the flag or multiple flags delimited by \n characters. Each Flag will be checked by the backend and then a message is returned picked by this code:

    FlagSubmissionResult.Ok => "VALID: Flag accepted!\n",
    FlagSubmissionResult.Invalid => "INVALID: You have submitted an invalid string!\n",
    FlagSubmissionResult.Duplicate => "RESUBMIT: You have already sent this flag!\n",
    FlagSubmissionResult.Own => "OWNFLAG: This flag belongs to you!\n",
    FlagSubmissionResult.Old => "OLD: You have submitted an old flag!\n",
    FlagSubmissionResult.UnknownError => "ERROR: An unexpected error occured :(\n",
    FlagSubmissionResult.InvalidSenderError => "ILLEGAL: Your IP address does not belong to any team's subnet!\n",
    FlagSubmissionResult.SpamError => "SPAM: You should send 1 flag per line!\n",

enoengine's People

Stargazers

 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

enoengine's Issues

Handle dead shells more gracefully

StartNewRound failed: Kein Prozess ist am anderen Ende der Pipe (System.IO.IOException)
   at System.ConsolePal.WindowsConsoleStream.Write(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
   at System.IO.StreamWriter.WriteLine(String value)
   at System.IO.SyncTextWriter.WriteLine(String value)
   at System.Console.WriteLine(String value)
   at EnoEngine.Game.CTF.InsertPutNewFlagsTasks(Int64 roundId, DateTime begin) in C:\\Users\\Benni\\repositories\\EnoEngine\\EnoEngine\\CTF.cs:line 115
   at EnoEngine.Game.CTF.<>c__DisplayClass5_1.<<StartNewRound>b__0>d.MoveNext() in C:\\Users\\Benni\\repositories\\EnoEngine\\EnoEngine\\CTF.cs:line 63
--- End of stack trace from previous location where exception was thrown ---
   at EnoEngine.Game.CTF.StartNewRound() in C:\\Users\\Benni\\repositories\\EnoEngine\\EnoEngine\\CTF.cs:line 67

Send roundLength to Checker on execution

EnoLauncher should call the Checker with the current roundLength (int), indicating how long a round will be.

This helps longer-running tasks to decide when they should be done.

Launcher doesn't like unavailable databases

InnerException:                                                                                                                                                                              [0/1019]
Exception while reading from stream (Npgsql.NpgsqlException)
   at Npgsql.NpgsqlReadBuffer.<>c__DisplayClass31_0.<<Ensure>g__EnsureLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlReadBuffer.cs:line 175
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlConnector.<>c__DisplayClass161_0.<<ReadMessage>g__ReadMessageLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.cs:line 955
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlConnector.<>c__DisplayClass161_0.<<ReadMessage>g__ReadMessageLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.cs:line 938
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.NpgsqlDataReader.NextResult(Boolean async, Boolean isConsuming) in C:\projects\npgsql\src\Npgsql\NpgsqlDataReader.cs:line 444
   at Npgsql.NpgsqlCommand.ExecuteDbDataReader(CommandBehavior behavior, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlCommand.cs:line 1215
   at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues, Cancellat
ionToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellat
ionToken) in C:\projects\npgsql-entityframeworkcore-postgresql\src\EFCore.PG\Storage\Internal\NpgsqlExecutionStrategy.cs:line 49
InnerException:
Attempted to read past the end of the stream. (System.IO.EndOfStreamException)
   at Npgsql.NpgsqlReadBuffer.<>c__DisplayClass31_0.<<Ensure>g__EnsureLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlReadBuffer.cs:line 154

Unhandled Exception: System.AggregateException: One or more errors occurred. (Connection refused 127.0.0.1:5432) ---> System.Net.Internals.SocketExceptionFactory+ExtendedSocketException: Connection
 refused 127.0.0.1:5432
   at System.Net.Sockets.Socket.EndConnect(IAsyncResult asyncResult)
   at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
   at Npgsql.TaskExtensions.WithCancellation(Task task, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\TaskExtensions.cs:line 85
   at Npgsql.TaskExtensions.WithTimeout(Task task, NpgsqlTimeout timeout) in C:\projects\npgsql\src\Npgsql\TaskExtensions.cs:line 51
   at Npgsql.NpgsqlConnector.ConnectAsync(NpgsqlTimeout timeout, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.cs:line 775
   at Npgsql.NpgsqlConnector.RawOpen(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.cs:line 550
   at Npgsql.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\NpgsqlConnector.cs:line 414
   at Npgsql.ConnectorPool.AllocateLong(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) in C:\projects\npgsql\src\Npgsql\ConnectorPool.cs:line 246
   at Npgsql.NpgsqlConnection.<>c__DisplayClass32_0.<<Open>g__OpenLong|0>d.MoveNext() in C:\projects\npgsql\src\Npgsql\NpgsqlConnection.cs:line 301
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.BufferlessMoveNext(DbContext _, Boolean buffer, CancellationToken cancellationToken)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellat
ionToken) in C:\projects\npgsql-entityframeworkcore-postgresql\src\EFCore.PG\Storage\Internal\NpgsqlExecutionStrategy.cs:line 49
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncQueryingEnumerable`1.AsyncEnumerator.MoveNext(CancellationToken cancellationToken)
   at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator`2.MoveNextCore(CancellationToken cancellationToken) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\Select.cs:line 106
   at System.Linq.AsyncEnumerable.AsyncIterator`1.MoveNext(CancellationToken cancellationToken) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\AsyncIterator.cs:line 98
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)
   at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable`1 source, TAccumulate seed, Func`3 accumulator, Func`2 resultSelector, CancellationToken cancellationToken
) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\Aggregate.cs:line 120
   at EnoCore.EnoDatabase.RetrievePendingCheckerTasks(Int32 maxAmount) in /opt/dockerfiles/EnoEngine/EnoCore/EnoDatabase.cs:line 239
   at EnoLauncher.Program.LauncherLoop(Dictionary`2 servicesDict) in /opt/dockerfiles/EnoEngine/EnoLauncher/Program.cs:line 34
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)
   at System.Threading.Tasks.Task.Wait()
   at EnoLauncher.Program.Start(Dictionary`2 servicesDict) in /opt/dockerfiles/EnoEngine/EnoLauncher/Program.cs:line 26
   at EnoLauncher.Program.Main(String[] args) in /opt/dockerfiles/EnoEngine/EnoLauncher/Program.cs:line 85

Socket disposal prints warning on shutdown

Harmless, but annoying:

warn: EnoEngine.EnoEngine[0]
      RunProductionEndpoint failed to handle connection: Cannot access a disposed object.
      Object name: 'System.Net.Sockets.Socket'. (System.ObjectDisposedException)
         at System.Net.Sockets.Socket.EndAccept(Byte[]& buffer, Int32& bytesTransferred, IAsyncResult asyncResult)
         at System.Net.Sockets.TcpListener.EndAcceptTcpClient(IAsyncResult asyncResult)
         at System.Net.Sockets.TcpListener.<>c.<AcceptTcpClientAsync>b__28_1(IAsyncResult asyncResult)
         at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
      --- End of stack trace from previous location where exception was thrown ---
         at EnoEngine.FlagSubmission.FlagSubmissionEndpoint.RunProductionEndpoint() in /services/EnoEngine/EnoEngine/FlagSubmissionEndpoint.cs:line 87

Engine should cope better with db hickup

2019-04-10T01:26:14 [Trace] [EnoCore.EnoDatabase] ApplyConfig()
2019-04-10T01:26:18 [Debug] [EnoEngine.Game.CTF] Handling end of round 42
2019-04-10T01:26:18 [Error] [EnoEngine.Game.CTF] StartNewRound failed: Source sequence doesn't contain any elements. (System.InvalidOperationException)
   at System.Linq.AsyncEnumerable.Single_[TSource](IAsyncEnumerable`1 source, CancellationToken cancellationToken) in D:\a\1\s\Ix.NET\Source\System.Interactive.Async\Single.cs:line 136
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.TaskResultAsyncEnumerable`1.Enumerator.MoveNext(CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext(CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteSingletonAsyncQuery[TResult](QueryContext queryContext, Func`2 compiledQuery, IDiagnosticsLogger`1 logger, Type contextType)
   at EnoCore.EnoDatabase.RecordServiceStates(Int64 roundId) in /opt/dockerfiles/EnoEngine/EnoCore/EnoDatabase.cs:line 528
   at EnoEngine.Game.CTF.HandleRoundEnd(Int64 roundId) in /opt/dockerfiles/EnoEngine/EnoEngine/CTF.cs:line 162
   at EnoEngine.Game.CTF.StartNewRound() in /opt/dockerfiles/EnoEngine/EnoEngine/CTF.cs:line 54

Reduce flag length

The entropy is useless now if the key is long enough, so we can drop it

Fix Config From Checkers

Actually use the config the checkers provide and also change the ctf.json so it works with them.

Improve statistics logging

We want to have data in fancy graphs! E.g.

  • a pie chart with submissions/$timeunit per team (with subcharts displaying ok/invalid/dup)
  • a graph displaying total submissions over time
  • a graph displaying queue contention
  • system load
  • ...

So far we have used Elk to display simple graphs. @5aint2ero could you advise what we should use? Can @enowars/engineering output well-formatted json and you do grafana magic and we get fancy graphs?

Mitigate the calculation-induced drift

We are awaiting a whole round despite losing time in calculation - we should be able to minimize the impact on the drift significantly by only awaiting the remaining round.

Include the first bloods in the scoreboard.json

I am not sure if this is a feature request or a bug report, but if this is already implemented it isn't working. Otherwise please implement it :D

Somewhat related: It would be a good idea to indicate whether the StoreIndex starts at 0 or 1 in the specification.

Resolve "The response ended prematurely" errors

LaunchCheckerTask failed: An error occurred while sending the request. (System.Net.Http.HttpRequestException)
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithNtConnectionAuthAsync(HttpConnection connection, HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
   at System.Net.Http.HttpClient.FinishSendAsyncBuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
   at EnoLauncher.Program.LaunchCheckerTask(CheckerTask task) in /services/EnoEngine/EnoLauncher/Program.cs:line 109
InnerException:
The response ended prematurely. (System.IO.IOException)
   at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)

After some debugging I have come to the following conclusion:

  • The issue occurs when the server closes a TCP connection for which Keep-Alive was enabled while a request from the EnoEngine is in-flight.
  • To the EnoEngine it appears that the server closed the connection after receiving the request but before sending a response.
  • The issue appears more often when the latency between EnoEngine and Checkers is high. The latency is the window in which the request and connection tear-down can overlap, when the latency is <1ms this is very unlikely to happen, with >300ms there is a realistic chance of this happening.
  • This cannot be resolved by the checker since there is no way to know whether a new request is being sent while closing the connection. Increasing the timeout reduces the chances of this happening (since the client usually sends new requests quite soon). It does not eliminate the risk and may lead to resource exhaustion on the checker though.
  • This can be resolved by the EnoEngine either by disabling Keep-Alive completely or dealing with the issue gracefully.

Handle invalid submission connections more gracefully

warn: EnoEngine.EnoEngine[0]
      RunProductionEndpoint failed to handle connection: Enumerator failed to MoveNextAsync. (System.InvalidOperationException)
         at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
         at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
         at EnoCore.EnoDatabase.GetTeamIdByPrefix(String attackerPrefixString) in /services/EnoEngine/EnoCore/EnoDatabase.cs:line 746
         at EnoEngine.FlagSubmission.FlagSubmissionEndpoint.RunProductionEndpoint() in /services/EnoEngine/EnoEngine/FlagSubmissionEndpoint.cs:line 99

We should send an error message and close the socket.

Enforce gapless round ids

postgres is not guaranteeing gapless autoincrements (if a transaction is aborted due to irresponsive disks or concurrent inserts), we should increase it manually

Point calculation impacts the next round

We are awaiting it before dispatching the current round's tasks, which in turn are sometimes significantly delayed. Will fix before friday, but not before tomorrow

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.