diff --git a/.gitignore b/.gitignore index 9903888..645ba09 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ export/ # Ignore public and private keys p2prc.publicKey p2prc.privateKey +p2prc.PublicKeyBareMetal # Ignore pem files *.pem diff --git a/Docs/haskell/P2PRC.html b/Docs/haskell/P2PRC.html new file mode 100644 index 0000000..9c67729 --- /dev/null +++ b/Docs/haskell/P2PRC.html @@ -0,0 +1,36 @@ +P2PRC
p2prc-0.1.0.0: P2PRC haskell library
CopyrightCopyright (C) 2024-2024 Jose Fernandes
LicenseGNU GPL, version 2 or above
MaintainerJose Fernandes <jf94.uk@gmail.com>
Stabilitybeta
Portabilityportable
Safe HaskellSafe-Inferred
LanguageGHC2021

P2PRC

Description

This library provides an interface to the P2Prc runtime.

This Module intends to export the main functions and data type definitions necessary to get started with the P2PRC api.

A minimal application will require the import of runP2PRC function that accepts a MapPortRequest value that exposes a specific port number and associates it with a domain name in the internet.

This is a small template to get quickly get started with this interface. We assume the user has already an application listening on the tcp socket "8080".

module Main where
+
+import P2PRC
+  ( runP2PRC
+  , MapPortRequest(MkMapPortRequest)
+  )
+
+main :: IO ()
+main =
+  runP2PRC
+    ( MkMapPortRequest 8080 "jose.akilan.io"
+    )
Synopsis

Functions

These are the available functions available to interact with the P2Prc environment, at a lower level of abstraction. + It is intended this way to give freedom to the developer to implement their own orchestration strategies.

runP2PRC #

Arguments

:: MapPortRequest

TCP Port Request

-> IO () 

This function starts and bootstraps the P2PRC runtime that associates the a specific host's machine port to a DNS address to expose a certain application to the P2PRC network. You will only need to also import the MkMapPortRequest data constructor to represent the this port request.

Example

Expand

This example demonstrates how it can be ran on the IO context:

 example :: IO ()
+ example = do
+   runP2PRC
+     ( MkMapPortRequest 8080 "jose.akilan.io"
+     )
+ 

getP2prcAPI :: IOEitherError P2PRCapi #

Warning: This function is currently unstable since it assumes that the Haskell program is executed from the P2PRC "haskell" subfolder and the "p2prc" executable in the the root folder.

This function cleans the previous running state (ensuring a pure P2PRC runtime state) and builds up a conditional P2PRCapi instance.

Example

Expand

The following example show how this function can be used to expose the runtime functionalities:

 example :: IOEitherError P2PRCapi
+ example = do
+
+   eitherP2prcAPI <- getP2prcAPI
+
+   case eitherP2prcAPI of
+     ( Right
+       ( MkP2PRCapi
+         { startServer     = startServer
+         , execInitConfig  = execInitConfig
+         , execListServers = execListServers
+         , execMapPort     = execMapPort
+         }
+       )) -> do
+
+       -- Your code logic
+
+     errValue -> errValue
+ 

Data Types

This section describes and explains the library's type system, more specifically, the interfaces and primitive types.

Interface data types

This section gives an overview on the runtime and host machine interfaces.

data P2PRCapi #

Lower level P2PRC Haskell api that exposes basic functionality necessary to joint the network.

Constructors

MkP2PRCapi 

Fields

newtype P2prcConfig #

Warning: This type is unstable at the moment due to the P2PRC's library error handling bug. For more information visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114#issuecomment-2474737015

This represents the server configuration that defines its attributes and behaviours in the network, as well as, the location of the runtime persistence artifacts.

Constructors

MkP2prConfig 

Fields

Instances

Instances details
FromJSON P2prcConfig # 
Instance details

Defined in JSON

Show P2prcConfig # 
Instance details

Defined in JSON

data MapPortRequest #

This defines the request required to create an association between a TCP socket port and a DNS server in the network. If successful, it makes a resource available in the network.

Constructors

MkMapPortRequest

P2PRC's port allocation request value

Fields

  • Int

    TCP socket number

  • String

    Network domain name

newtype MapPortResponse #

Warning: This newtype is unstable at the moment due to the P2PRC's library error handling bug. For more information visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114#issuecomment-2474737015

This represents P2PRC's response to the TCP port and DNS address allocation. This value will confirm the successful allocation and return information about it.

Constructors

MkMapPortResponse

Allocation information value

Fields

  • String

    Column separated Host's IP address and Port String

Instances

Instances details
FromJSON MapPortResponse # 
Instance details

Defined in JSON

Show MapPortResponse # 
Instance details

Defined in JSON

Primitive data types

These types represent the core data that is communicated between requests and the runtime.

newtype IPAddressTable #

Warning: This newtype is highly unstable due to undergoing work on improving P2PRC's server api. For more information, visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114

This is a wrapper value that parses a json key value from the list of ip addresses in the network.

Constructors

MkIPAddressTable

Wrapping constructor

Fields

Instances

Instances details
FromJSON IPAddressTable # 
Instance details

Defined in JSON

Show IPAddressTable # 
Instance details

Defined in JSON

data ServerInfo #

Warning: This type is highly unstable due to undergoing work on improving P2PRC's server api. For more information, visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114

This is a record that keeps track of the current state of every node in the network. It is crucial information required for orchestration strategies.

Constructors

MkServerInfo 

Fields

Instances

Instances details
FromJSON ServerInfo # 
Instance details

Defined in JSON

Show ServerInfo # 
Instance details

Defined in JSON

data IPAddress #

This is a simple representation of the IP address of nodes in the network.

Constructors

MkIPv4 String

IP version 4 address

MkIPv6 String

IP version 6 address

Instances

Instances details
Show IPAddress # 
Instance details

Defined in JSON

data Error #

Haskell-side Error value. This type is designed to parse and track System and P2PRC's error signals in a safe and effective manner.

It does have an MkUnknownError value which is meant to be warn about new kinds of error not yet accounted in this client. Github issues and pull requests are very welcome to improve error handling by parsing more types of errors.

Constructors

MkCLISystemError

This is a CLI System Error

Fields

MkErrorSpawningProcess

Spawing process error

Fields

  • String

    Spawning executable name

MkUnknownError

This is an unparsed P2PRC's error

Fields

Instances

Instances details
Show Error # 
Instance details

Defined in Error

Methods

showsPrec :: Int -> Error -> ShowS #

show :: Error -> String #

showList :: [Error] -> ShowS #

Type Synonyms

This section is reserved to some useful type synonyms that add significant ergonomics.

type IOEitherError a = IO (Either Error a) #

Type synonym for an IO action with either returns an Error or a parsed value

\ No newline at end of file diff --git a/Docs/haskell/P2PRC.md b/Docs/haskell/P2PRC.md new file mode 100644 index 0000000..a5c96dd --- /dev/null +++ b/Docs/haskell/P2PRC.md @@ -0,0 +1,120 @@ +
+ +p2prc-0.1.0.0: P2PRC haskell library + +- [Contents](index.html) +- [Index](doc-index.html) + +
+ +
+ +
+ +| | | +|--------------|-----------------------------------------| +| Copyright | Copyright (C) 2006-2024 John MacFarlane | +| License | GNU GPL, version 2 or above | +| Maintainer | John MacFarlane \ | +| Stability | alpha | +| Portability | portable | +| Safe Haskell | Safe-Inferred | +| Language | GHC2021 | + +P2PRC + +
+ +
+ +Description + +
+ +This helper module exports the main writers, readers, and data structure +definitions from the Pandoc libraries. + +A typical application will chain together a reader and a writer to +convert strings from one format to another. For example, the following +simple program will act as a filter converting markdown fragments to +reStructuredText, using reference-style links instead of inline links: + + module Main where + import Text.Pandoc + import Data.Text (Text) + import qualified Data.Text.IO as T + + mdToRST :: Text -> IO Text + mdToRST txt = runIOorExplode $ + readMarkdown def txt + >>= writeRST def{ writerReferenceLinks = True } + + main :: IO () + main = do + T.getContents >>= mdToRST >>= T.putStrLn + +
+ +
+ +
+ +Synopsis + +- [runP2PRC](#v:runP2PRC) :: + [MapPortRequest](P2PRC.html#t:MapPortRequest "P2PRC") -\> + [IO]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/System-IO.html#t:IO "System.IO") + () +- data [MapPortRequest](#t:MapPortRequest) + = [MkMapPortRequest](#v:MkMapPortRequest) + [Int]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-Int.html#t:Int "Data.Int") + [String]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-String.html#t:String "Data.String") + +
+ +
+ +# Documentation + +
+ +runP2PRC :: +[MapPortRequest](P2PRC.html#t:MapPortRequest "P2PRC") -\> +[IO]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/System-IO.html#t:IO "System.IO") +() # + +
+ +Hello World + +
+ +
+ +
+ +data MapPortRequest +# + +
+ +Constructors + +| | | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----| +| MkMapPortRequest [Int]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-Int.html#t:Int "Data.Int") [String]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-String.html#t:String "Data.String") |   | + +
+ +
+ +
+ +
+ + diff --git a/Docs/haskell/README.md b/Docs/haskell/README.md new file mode 100644 index 0000000..a5c96dd --- /dev/null +++ b/Docs/haskell/README.md @@ -0,0 +1,120 @@ +
+ +p2prc-0.1.0.0: P2PRC haskell library + +- [Contents](index.html) +- [Index](doc-index.html) + +
+ +
+ +
+ +| | | +|--------------|-----------------------------------------| +| Copyright | Copyright (C) 2006-2024 John MacFarlane | +| License | GNU GPL, version 2 or above | +| Maintainer | John MacFarlane \ | +| Stability | alpha | +| Portability | portable | +| Safe Haskell | Safe-Inferred | +| Language | GHC2021 | + +P2PRC + +
+ +
+ +Description + +
+ +This helper module exports the main writers, readers, and data structure +definitions from the Pandoc libraries. + +A typical application will chain together a reader and a writer to +convert strings from one format to another. For example, the following +simple program will act as a filter converting markdown fragments to +reStructuredText, using reference-style links instead of inline links: + + module Main where + import Text.Pandoc + import Data.Text (Text) + import qualified Data.Text.IO as T + + mdToRST :: Text -> IO Text + mdToRST txt = runIOorExplode $ + readMarkdown def txt + >>= writeRST def{ writerReferenceLinks = True } + + main :: IO () + main = do + T.getContents >>= mdToRST >>= T.putStrLn + +
+ +
+ +
+ +Synopsis + +- [runP2PRC](#v:runP2PRC) :: + [MapPortRequest](P2PRC.html#t:MapPortRequest "P2PRC") -\> + [IO]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/System-IO.html#t:IO "System.IO") + () +- data [MapPortRequest](#t:MapPortRequest) + = [MkMapPortRequest](#v:MkMapPortRequest) + [Int]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-Int.html#t:Int "Data.Int") + [String]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-String.html#t:String "Data.String") + +
+ +
+ +# Documentation + +
+ +runP2PRC :: +[MapPortRequest](P2PRC.html#t:MapPortRequest "P2PRC") -\> +[IO]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/System-IO.html#t:IO "System.IO") +() # + +
+ +Hello World + +
+ +
+ +
+ +data MapPortRequest +# + +
+ +Constructors + +| | | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----| +| MkMapPortRequest [Int]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-Int.html#t:Int "Data.Int") [String]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-String.html#t:String "Data.String") |   | + +
+ +
+ +
+ +
+ + diff --git a/Docs/haskell/doc-index.html b/Docs/haskell/doc-index.html new file mode 100644 index 0000000..f5cfe01 --- /dev/null +++ b/Docs/haskell/doc-index.html @@ -0,0 +1 @@ +p2prc-0.1.0.0: P2PRC haskell library (Index)
p2prc-0.1.0.0: P2PRC haskell library

Index

bareMetalSSHPortP2PRC
customInformationP2PRC
downloadP2PRC
ErrorP2PRC
escapeImplementationP2PRC
execInitConfigP2PRC
execListServersP2PRC
execMapPortP2PRC
getP2prcAPIP2PRC
IOEitherErrorP2PRC
ipP2PRC
IPAddressP2PRC
IPAddressTableP2PRC
latencyP2PRC
machineNameP2PRC
MapPortRequestP2PRC
MapPortResponseP2PRC
MkCLISystemErrorP2PRC
MkErrorSpawningProcessP2PRC
MkIPAddressTableP2PRC
MkIPv4P2PRC
MkIPv6P2PRC
MkMapPortRequestP2PRC
MkMapPortResponseP2PRC
MkP2PRCapiP2PRC
MkP2prConfigP2PRC
MkServerInfoP2PRC
MkUnknownErrorP2PRC
nameP2PRC
natP2PRC
P2PRCapiP2PRC
P2prcConfigP2PRC
runP2PRCP2PRC
ServerInfoP2PRC
serverPortP2PRC
startServerP2PRC
uploadP2PRC
\ No newline at end of file diff --git a/Docs/haskell/doc-index.md b/Docs/haskell/doc-index.md new file mode 100644 index 0000000..11b10db --- /dev/null +++ b/Docs/haskell/doc-index.md @@ -0,0 +1,30 @@ +
+ +p2prc-0.1.0.0: P2PRC haskell library + +- [Contents](index.html) +- [Index](doc-index.html) + +
+ +
+ +
+ +Index + +| | | +|------------------|------------------------------------------------| +| MapPortRequest | [P2PRC](P2PRC.html#t:MapPortRequest "P2PRC") | +| MkMapPortRequest | [P2PRC](P2PRC.html#v:MkMapPortRequest "P2PRC") | +| runP2PRC | [P2PRC](P2PRC.html#v:runP2PRC "P2PRC") | + +
+ +
+ + diff --git a/Docs/haskell/haddock-bundle.min.js b/Docs/haskell/haddock-bundle.min.js new file mode 100644 index 0000000..4b2fa13 --- /dev/null +++ b/Docs/haskell/haddock-bundle.min.js @@ -0,0 +1,2 @@ +!function i(s,a,l){function c(t,e){if(!a[t]){if(!s[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(u)return u(t,!0);var o=new Error("Cannot find module '"+t+"'");throw o.code="MODULE_NOT_FOUND",o}var r=a[t]={exports:{}};s[t][0].call(r.exports,function(e){return c(s[t][1][e]||e)},r,r.exports,i,s,a,l)}return a[t].exports}for(var u="function"==typeof require&&require,e=0;e element with id '"+e+"'");return t}function x(){return u.defaultInstanceState==i.Open}function w(e){for(var t=S(e.target.id),n=t.element.open,o=0,r=t.toggles;owindow.innerHeight?this.searchResults.scrollTop+=e.bottom-window.innerHeight+80:e.topn)return u(e,this.pattern,o);var r=this.options,i=r.location,s=r.distance,a=r.threshold,l=r.findAllMatches,c=r.minMatchCharLength;return d(e,this.pattern,this.patternAlphabet,{location:i,distance:s,threshold:a,findAllMatches:l,minMatchCharLength:c})}}]),y}();e.exports=r},function(e,t,n){"use strict";var u=n(0);e.exports=function(e,t){return function e(t,n,o){if(n){var r=n.indexOf("."),i=n,s=null;-1!==r&&(i=n.slice(0,r),s=n.slice(r+1));var a=t[i];if(null!=a)if(s||"string"!=typeof a&&"number"!=typeof a)if(u(a))for(var l=0,c=a.length;l 0 and <= 1");p=p.name}else a[p]={weight:1};this._analyze({key:p,value:this.options.getFn(u,p),record:u,index:l},{resultMap:o,results:r,tokenSearchers:e,fullSearcher:t})}return{weights:a,results:r}}},{key:"_analyze",value:function(e,t){var n=e.key,o=e.arrayIndex,r=void 0===o?-1:o,i=e.value,s=e.record,a=e.index,l=t.tokenSearchers,c=void 0===l?[]:l,u=t.fullSearcher,d=void 0===u?[]:u,h=t.resultMap,p=void 0===h?{}:h,f=t.results,v=void 0===f?[]:f;if(null!=i){var g=!1,m=-1,y=0;if("string"==typeof i){this._log("\nKey: "+(""===n?"-":n));var _=d.search(i);if(this._log('Full text: "'+i+'", score: '+_.score),this.options.tokenize){for(var b=i.split(this.options.tokenSeparator),k=[],S=0;S=c.length;if(this._log("\nCheck Matches: "+T),(g||_.isMatch)&&T){var N=p[a];N?N.output.push({key:n,arrayIndex:r,value:i,score:A,matchedIndices:_.matchedIndices}):(p[a]={item:s,output:[{key:n,arrayIndex:r,value:i,score:A,matchedIndices:_.matchedIndices}]},v.push(p[a]))}}else if(V(i))for(var P=0,j=i.length;Pp2prc-0.1.0.0: P2PRC haskell library
p2prc-0.1.0.0: P2PRC haskell library

p2prc-0.1.0.0: P2PRC haskell library

Implements a client interface to the P2PRC networking runtime

Modules

p2prc-0.1.0.0

\ No newline at end of file diff --git a/Docs/haskell/index.md b/Docs/haskell/index.md new file mode 100644 index 0000000..5a74ed5 --- /dev/null +++ b/Docs/haskell/index.md @@ -0,0 +1,44 @@ +
+ +p2prc-0.1.0.0: P2PRC haskell library + +- [Contents](index.html) +- [Index](doc-index.html) + +
+ +
+ +
+ +# p2prc-0.1.0.0: P2PRC haskell library + +
+ +Implements a client interface to the P2PRC networking runtime + +
+ +
+ +
+ +Modules + +
+ +p2prc-0.1.0.0 + +-  [P2PRC](P2PRC.html) + +
+ +
+ +
+ + diff --git a/Docs/haskell/linuwial.css b/Docs/haskell/linuwial.css new file mode 100644 index 0000000..4164b84 --- /dev/null +++ b/Docs/haskell/linuwial.css @@ -0,0 +1,881 @@ +/* @group Fundamentals */ + +* { margin: 0; padding: 0 } + +/* Is this portable? */ +html { + background-color: white; + width: 100%; + height: 100%; +} + +body { + background: #fefefe; + color: #111; + text-align: left; + min-height: 100vh; + position: relative; + -webkit-text-size-adjust: 100%; + -webkit-font-feature-settings: "kern" 1, "liga" 0; + -moz-font-feature-settings: "kern" 1, "liga" 0; + -o-font-feature-settings: "kern" 1, "liga" 0; + font-feature-settings: "kern" 1, "liga" 0; + letter-spacing: 0.0015rem; +} + +#content a { + overflow-wrap: break-word; +} + +p { + margin: 0.8em 0; +} + +ul, ol { + margin: 0.8em 0 0.8em 2em; +} + +dl { + margin: 0.8em 0; +} + +dt { + font-weight: bold; +} +dd { + margin-left: 2em; +} + +a { text-decoration: none; } +a[href]:link { color: #9E358F; } +a[href]:visited {color: #6F5F9C; } +a[href]:hover { text-decoration:underline; } + +a[href].def:link, a[href].def:visited { color: rgba(69, 59, 97, 0.8); } +a[href].def:hover { color: rgb(78, 98, 114); } + +/* @end */ + +/* @group Show and hide with JS */ + +body.js-enabled .hide-when-js-enabled { + display: none; +} + +/* @end */ + + +/* @group responsive */ + +#package-header .caption { + margin: 0px 1em 0 2em; +} + +@media only screen and (min-width: 1280px) { + #content { + width: 63vw; + max-width: 1450px; + } + + #table-of-contents { + position: fixed; + max-width: 10vw; + top: 10.2em; + left: 2em; + bottom: 1em; + overflow-y: auto; + } + + #synopsis { + display: block; + position: fixed; + float: left; + top: 5em; + bottom: 1em; + right: 0; + max-width: 65vw; + overflow-y: auto; + /* Ensure that synopsis covers everything (including MathJAX markup) */ + z-index: 1; + } + + #synopsis .show { + border: 1px solid #5E5184; + padding: 0.7em; + max-height: 65vh; + } + +} + +@media only screen and (max-width: 1279px) { + #content { + width: 80vw; + } + + #synopsis { + display: block; + padding: 0; + position: relative; + margin: 0; + width: 100%; + } +} + +@media only screen and (max-width: 999px) { + #content { + width: 93vw; + } +} + + +/* menu for wider screens + + Display the package name at the left and the menu links at the right, + inline with each other: + The package name Source . Contents . Index +*/ +@media only screen and (min-width: 1000px) { + #package-header { + text-align: left; + white-space: nowrap; + height: 40px; + padding: 4px 1.5em 0px 1.5em; + overflow: visible; + + display: flex; + justify-content: space-between; + align-items: center; + } + + #package-header .caption { + display: inline-block; + margin: 0; + } + + #package-header ul.links { + margin: 0; + display: inline-table; + } + + #package-header .caption + ul.links { + margin-left: 1em; + } +} + +/* menu for smaller screens + +Display the package name on top of the menu links and center both elements: + The package name + Source . Contents . Index +*/ +@media only screen and (max-width: 999px) { + #package-header { + text-align: center; + padding: 6px 0 4px 0; + overflow: hidden; + } + + #package-header ul.links { + display: block; + text-align: center; + margin: 0; + + /* Hide scrollbar but allow scrolling menu links horizontally */ + white-space: nowrap; + overflow-x: auto; + overflow-y: hidden; + margin-bottom: -17px; + height: 50px; + } + + #package-header .caption { + display: block; + margin: 4px 0; + text-align: center; + } + + #package-header ul.links::-webkit-scrollbar { + display: none; + } + + #package-header ul.links li:first-of-type { + padding-left: 1em; + } + + #package-header ul.links li:last-of-type { + /* + The last link of the menu should offer the same distance to the right + as the #package-header enforces at the left. + */ + padding-right: 1em; + } + + #package-header .caption + ul.links { + padding-top: 9px; + } + + #module-header table.info { + float: none; + top: 0; + margin: 0 auto; + overflow: hidden; + max-width: 80vw; + } +} + +/* @end */ + + +/* @group Fonts & Sizes */ + +/* Basic technique & IE workarounds from YUI 3 + For reasons, see: + http://yui.yahooapis.com/3.1.1/build/cssfonts/fonts.css + */ + + body, button { + font: 400 14px/1.4 'PT Sans', + /* Fallback Font Stack */ + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen-Sans, + Cantarell, + 'Helvetica Neue', + sans-serif; + *font-size: medium; /* for IE */ + *font:x-small; /* for IE in quirks mode */ + } + +h1 { font-size: 146.5%; /* 19pt */ } +h2 { font-size: 131%; /* 17pt */ } +h3 { font-size: 116%; /* 15pt */ } +h4 { font-size: 100%; /* 13pt */ } +h5 { font-size: 100%; /* 13pt */ } + +table { + font-size:inherit; + font:100%; +} + +pre, code, kbd, samp, tt, .src { + font-family:monospace; +} + +.links, .link { + font-size: 85%; /* 11pt */ +} + +#module-header .caption { + font-size: 182%; /* 24pt */ +} + +#module-header .caption sup { + font-size: 80%; + font-weight: normal; +} + +#package-header #page-menu a:link, #package-header #page-menu a:visited { color: white; } + + +.info { + font-size: 90%; +} + + +/* @end */ + +/* @group Common */ + +.caption, h1, h2, h3, h4, h5, h6, summary { + font-weight: bold; + color: #5E5184; + margin: 1.5em 0 1em 0; +} + + +* + h1, * + h2, * + h3, * + h4, * + h5, * + h6 { + margin-top: 2em; +} + +h1 + h2, h2 + h3, h3 + h4, h4 + h5, h5 + h6 { + margin-top: inherit; +} + +ul li + li { + margin-top: 0.2rem; +} + +ul + p { + margin-top: 0.93em; +} + +p + ul { + margin-top: 0.5em; +} + +p { + margin-top: 0.7rem; +} + +ul, ol { + margin: 0.8em 0 0.8em 2em; +} + +ul.links { + list-style: none; + text-align: left; + font-size: 0.95em; +} + +#package-header ul.links, #package-header ul.links button { + font-size: 1rem; +} + +ul.links li { + display: inline; + white-space: nowrap; + padding: 0; +} + +ul.links > li + li:before { + content: '\00B7'; +} + +ul.links li a { + padding: 0.2em 0.5em; +} + +.hide { display: none; } +.show { display: inherit; } +.clear { clear: both; } + +.collapser:before, .expander:before, .noexpander:before { + font-size: 1.2em; + color: #9C5791; + display: inline-block; + padding-right: 7px; +} + +.collapser:before { + content: '▿'; +} +.expander:before { + content: '▹'; +} +.noexpander:before { + content: '▿'; + visibility: hidden; +} + +.collapser, .expander { + cursor: pointer; +} + +.instance.collapser, .instance.expander { + margin-left: 0px; + background-position: left center; + min-width: 9px; + min-height: 9px; +} + +summary { + cursor: pointer; + outline: none; +} + +pre { + padding: 0.5rem 1rem; + margin: 1em 0 0 0; + background-color: #f7f7f7; + overflow: auto; + border: 1px solid #ddd; + border-radius: 0.3em; +} + +pre + p { + margin-top: 1em; +} + +pre + pre { + margin-top: 0.5em; +} + +blockquote { + border-left: 3px solid #c7a5d3; + background-color: #eee4f1; + margin: 0.5em; + padding: 0.0005em 0.3em 0.5em 0.5em; +} + +.src { + background: #f2f2f2; + padding: 0.2em 0.5em; +} + +.keyword { font-weight: normal; } +.def { font-weight: bold; } + +@media print { + #footer { display: none; } +} + +/* @end */ + +/* @group Page Structure */ + +#content { + margin: 3em auto 6em auto; + padding: 0; +} + +#package-header { + background: #5E5184; + border-bottom: 5px solid rgba(69, 59, 97, 0.5); + color: #ddd; + position: relative; + font-size: 1.2em; + text-align: left; + margin: 0 auto; +} + +#package-header .caption { + color: white; + font-style: normal; + font-size: 1rem; + font-weight: bold; +} + +#module-header .caption { + font-weight: bold; + border-bottom: 1px solid #ddd; +} + +table.info { + float: right; + padding: 0.5em 1em; + border: 1px solid #ddd; + color: rgb(78,98,114); + background-color: #fff; + max-width: 60%; + border-spacing: 0; + position: relative; + top: -0.78em; + margin: 0 0 0 2em; +} + +.info th { + padding: 0 1em 0 0; + text-align: right; +} + +#style-menu li { + display: block; + border-style: none; + list-style-type: none; +} + +#footer { + background: #ededed; + border-top: 1px solid #aaa; + padding: 0.5em 0; + color: #222; + text-align: center; + width: 100%; + height: 3em; + margin-top: 3em; + position: relative; + clear: both; +} + +/* @end */ + +/* @group Front Matter */ + +#synopsis .caption, +#contents-list .caption { + font-size: 1rem; +} + +#synopsis, #table-of-contents { + font-size: 16px; +} + +#contents-list { + background: #f4f4f4; + padding: 1em; + margin: 0; +} + +#contents-list .caption { + text-align: left; + margin: 0; +} + +#contents-list ul { + list-style: none; + margin: 0; + margin-top: 10px; + font-size: 14px; +} + +#contents-list ul ul { + margin-left: 1.5em; +} + +#description .caption { + display: none; +} + +#synopsis summary { + display: block; + float: right; + width: 29px; + color: rgba(255,255,255,0); + height: 110px; + margin: 0; + font-size: 1px; + padding: 0; + background: url(synopsis.png) no-repeat 0px -8px; +} + +#synopsis details[open] > summary { + background: url(synopsis.png) no-repeat -75px -8px; +} + +#synopsis details:not([open]) > ul { + visibility: hidden; +} + +#synopsis ul { + height: 100%; + overflow: auto; + padding: 0.5em; + margin: 0; +} + +#synopsis ul ul { + overflow: hidden; +} + +#synopsis ul, +#synopsis ul li.src { + background-color: rgb(250,247,224); + white-space: nowrap; + list-style: none; + margin-left: 0; +} + +#interface td.src { + white-space: nowrap; +} + +/* @end */ + +/* @group Main Content */ + +#interface div.top + div.top { + margin-top: 1.5em; +} + +#interface p + div.top, +#interface h1 + div.top, +#interface h2 + div.top, +#interface h3 + div.top, +#interface h4 + div.top, +#interface h5 + div.top { + margin-top: 1em; +} +#interface .src .selflink, +#interface .src .link { + float: right; + color: #888; + padding: 0 7px; + -moz-user-select: none; + font-weight: bold; + line-height: 30px; +} +#interface .src .selflink { + margin: 0 -0.5em 0 0.5em; +} + +#interface span.fixity { + color: #919191; + border-left: 1px solid #919191; + padding: 0.2em 0.5em 0.2em 0.5em; + margin: 0 -1em 0 1em; +} + +#interface span.rightedge { + border-left: 1px solid #919191; + padding: 0.2em 0 0.2em 0; + margin: 0 0 0 1em; +} + +#interface table { border-spacing: 2px; } +#interface td { + vertical-align: top; + padding-left: 0.5em; +} + +#interface td.doc p { + margin: 0; +} +#interface td.doc p + p { + margin-top: 0.8em; +} + +.doc table { + border-collapse: collapse; + border-spacing: 0px; +} + +.doc th, +.doc td { + padding: 5px; + border: 1px solid #ddd; +} + +.doc th { + background-color: #f0f0f0; +} + +.clearfix:after { + clear: both; + content: " "; + display: block; + height: 0; + visibility: hidden; +} + +.subs, .top > .doc, .subs > .doc { + padding-left: 1em; + border-left: 1px solid gainsboro; + margin-bottom: 1em; +} + +.top .subs { + margin-bottom: 0.6em; +} + +.subs.fields ul { + list-style: none; + display: table; + margin: 0; +} + +.subs.fields ul li { + display: table-row; +} + +.subs ul li dfn { + display: table-cell; + font-style: normal; + font-weight: bold; + margin: 1px 0; + white-space: nowrap; +} + +.subs ul li > .doc { + display: table-cell; + padding-left: 0.5em; + margin-bottom: 0.5em; +} + +.subs ul li > .doc p { + margin: 0; +} + +.subs .subs p.src { + border: none; + background-color: #f8f8f8; +} + +.subs .subs .caption { + margin-top: 1em ; + margin-bottom: 0px; +} + +.subs p.caption { + margin-top: 0; +} + +.subs .subs .caption + .src { + margin: 0px; + margin-top: 8px; +} + +.subs .subs .src + .src { + margin: 7px 0 0 0; +} + +/* Render short-style data instances */ +.inst ul { + height: 100%; + padding: 0.5em; + margin: 0; +} + +.inst, .inst li { + list-style: none; + margin-left: 1em; +} + +/* Workaround for bug in Firefox (issue #384) */ +.inst-left { + float: left; +} + +.top p.src { + border-bottom: 3px solid #e5e5e5; + line-height: 2rem; + margin-bottom: 1em; +} + +.warning { + color: red; +} + +.arguments { + margin-top: -0.4em; +} +.arguments .caption { + display: none; +} + +.fields { padding-left: 1em; } + +.fields .caption { display: none; } + +.fields p { margin: 0 0; } + +/* this seems bulky to me +.methods, .constructors { + background: #f8f8f8; + border: 1px solid #eee; +} +*/ + +/* @end */ + +/* @group Auxillary Pages */ + + +.extension-list { + list-style-type: none; + margin-left: 0; +} + +#mini { + margin: 0 auto; + padding: 0 1em 1em; +} + +#mini > * { + font-size: 93%; /* 12pt */ +} + +#mini #module-list .caption, +#mini #module-header .caption { + font-size: 125%; /* 15pt */ +} + +#mini #interface h1, +#mini #interface h2, +#mini #interface h3, +#mini #interface h4 { + font-size: 109%; /* 13pt */ + margin: 1em 0 0; +} + +#mini #interface .top, +#mini #interface .src { + margin: 0; +} + +#mini #module-list ul { + list-style: none; + margin: 0; +} + +#alphabet ul { + list-style: none; + padding: 0; + margin: 0.5em 0 0; + text-align: center; +} + +#alphabet li { + display: inline; + margin: 0 0.25em; +} + +#alphabet a { + font-weight: bold; +} + +#index .caption, +#module-list .caption { font-size: 131%; /* 17pt */ } + +#index table { + margin-left: 2em; +} + +#index .src { + font-weight: bold; +} +#index .alt { + font-size: 77%; /* 10pt */ + font-style: italic; + padding-left: 2em; +} + +#index td + td { + padding-left: 1em; +} + +#module-list ul { + list-style: none; + margin: 0 0 0 2em; +} + +#module-list li { + clear: right; +} + +#module-list span.collapser, +#module-list span.expander { + background-position: 0 0.3em; +} + +#module-list .package { + float: right; +} + +:target { + background: -webkit-linear-gradient(top, transparent 0%, transparent 65%, #fbf36d 60%, #fbf36d 100%); + background: -moz-linear-gradient(top, transparent 0%, transparent 65%, #fbf36d 60%, #fbf36d 100%); + background: -o-linear-gradient(top, transparent 0%, transparent 65%, #fbf36d 60%, #fbf36d 100%); + background: -ms-linear-gradient(top, transparent 0%, transparent 65%, #fbf36d 60%, #fbf36d 100%); + background: linear-gradient(to bottom, transparent 0%, transparent 65%, #fbf36d 60%, #fbf36d 100%); +} + +:target:hover { + background: -webkit-linear-gradient(top, transparent 0%, transparent 0%, #fbf36d 0%, #fbf36d 100%); + background: -moz-linear-gradient(top, transparent 0%, transparent 0%, #fbf36d 0%, #fbf36d 100%); + background: -o-linear-gradient(top, transparent 0%, transparent 0%, #fbf36d 0%, #fbf36d 100%); + background: -ms-linear-gradient(top, transparent 0%, transparent 0%, #fbf36d 0%, #fbf36d 100%); + background: linear-gradient(to bottom, transparent 0%, transparent 0%, #fbf36d 0%, #fbf36d 100%); +} + +/* @end */ + +/* @group Dropdown menus */ + +#preferences-menu, #style-menu { + width: 25em; + overflow-y: auto; +} + +/* @end */ diff --git a/Docs/haskell/meta.json b/Docs/haskell/meta.json new file mode 100644 index 0000000..4e0bb86 --- /dev/null +++ b/Docs/haskell/meta.json @@ -0,0 +1 @@ +{"haddock_version":"2.29.2"} \ No newline at end of file diff --git a/Docs/haskell/p2prc.haddock b/Docs/haskell/p2prc.haddock new file mode 100644 index 0000000..af23673 Binary files /dev/null and b/Docs/haskell/p2prc.haddock differ diff --git a/Docs/haskell/quick-jump.css b/Docs/haskell/quick-jump.css new file mode 100644 index 0000000..cf10eee --- /dev/null +++ b/Docs/haskell/quick-jump.css @@ -0,0 +1,221 @@ +/* @group Fundamentals */ + +.hidden { + display: none; +} + +/* @end */ + +/* @group Search box layout */ + +#search { + position: fixed; + top: 3.2em; + bottom: 0; + left: calc(50% - 22em); + width: 44em; + z-index: 1000; + overflow-y: auto; +} + +@media only screen and (max-width: 999px) { + #search { + top: 5.7em; + } +} + +#search-form, #search-results { + box-shadow: 2px 2px 6px rgb(199, 204, 208); + pointer-events: all; +} + +#search-form input { + font-size: 1.25em; line-height: 2.3em; height: 2.4em; + display: block; + box-sizing: border-box; + width: 100%; + margin: 0; + padding: 0 0.75em; + border: 0.05em solid rgb(151, 179, 202); +} + +#search input:focus { + outline: none; +} + +#search p.error { + color: rgb(107, 24, 24); + font-weight: bold; +} + +#search-results { + box-sizing: border-box; + border: 0.05em solid #b2d5fb; + background: #e8f3ff; + max-height: 80%; + overflow: scroll; +} + +#search-form input + #search-results { + border-top: none; + top: 3em; + max-height: calc(100% - 3em); +} + +/* @end */ + +/* @group search results */ + +#search-results > ul { + margin: 0; + list-style: none; +} + +#search-results > ul > li, +#search-results > p, +#search-results > table { + padding: 0.5em 1em; + margin: 0; +} + +#search-results > ul > li { + border-bottom: 1px solid #b2d5fb; +} + +#search-results > ul > li > ul { + list-style: none; +} + +.search-module h4 { + margin: 0; +} + +.search-module > ul { + margin: 0.5em 0 0.5em 2em; +} + +.search-module > ul > li > a[href] { + display: block; + color: inherit; + padding: 0.25em 0.5em; +} + +.search-module > ul > li > a[href].active-link { + background: #faf9dc; +} + +.search-module a[href]:hover { + text-decoration: none; +} + +.search-result a a { + pointer-events: none; +} + +.search-result ul.subs { + display: inline-block; + margin: 0; padding: 0; +} + +.search-result ul.subs li { + display: none; +} + +.search-result ul.subs::after { + display: inline-block; + content: "..."; + color: rgb(78,98,114); + margin: 0 0.25em; +} + +.more-results { + color: rgb(99, 141, 173); + position: relative; +} + +.more-results::before { + content: "+"; + display: inline-block; + color: #b2d5fb; + font-weight: bold; + font-size: 1.25em; line-height: inherit; + position: absolute; + left: -1em; +} + +/* @end */ + +/* @group Keyboard shortcuts table */ + +.keyboard-shortcuts { + line-height: 1.6em; +} + +.keyboard-shortcuts th { + color: rgb(78,98,114); +} + +.keyboard-shortcuts td:first-child, +.keyboard-shortcuts th:first-child { + text-align: right; + padding-right: 0.6em; +} + +.key { + display: inline-block; + font-size: 0.9em; + min-width: 0.8em; line-height: 1.2em; + text-align: center; + background: #b2d5fb; + border: 1px solid #74a3d6; + padding: 0 0.2em; + margin: 0 0.1em; +} + +/* @end */ + +/* @group Dropdown menus */ + +/* Based on #search styling above. */ + +.dropdown-menu { + position: fixed; + /* Not robust to window size changes. */ + top: 3.2em; + right: 0; + /* To display on top of synopsis menu on right side. */ + z-index: 1000; + border: 0.05em solid #b2d5fb; + background: #e8f3ff; +} + +@media only screen and (max-width: 999px) { + .dropdown-menu { + top: 5.7em; + } +} + +.dropdown-menu * { + margin: 0.1em; +} + +.dropdown-menu button { + border: 1px #5E5184 solid; + border-radius: 3px; + background: #5E5184; + padding: 3px; + color: #f4f4f4; + min-width: 6em; +} + +.dropdown-menu button:hover { + color: #5E5184; + background: #f4f4f4; +} + +.dropdown-menu button:active { + color: #f4f4f4; + background: #5E5184; +} + +/* @end */ diff --git a/Docs/haskell/synopsis.png b/Docs/haskell/synopsis.png new file mode 100644 index 0000000..85fb86e Binary files /dev/null and b/Docs/haskell/synopsis.png differ diff --git a/haskell/.gitignore b/haskell/.gitignore new file mode 100644 index 0000000..07ead31 --- /dev/null +++ b/haskell/.gitignore @@ -0,0 +1,15 @@ +CHANGELOG.md +client +config.json +dist-newstyle +p2p +p2prc.privateKey +p2prc.PublicKeyBareMetal +plugin +server + +haddocks/ + +key.pem +cert.pem +cabal.project.local diff --git a/haskell/README.md b/haskell/README.md new file mode 100644 index 0000000..a5c96dd --- /dev/null +++ b/haskell/README.md @@ -0,0 +1,120 @@ +
+ +p2prc-0.1.0.0: P2PRC haskell library + +- [Contents](index.html) +- [Index](doc-index.html) + +
+ +
+ +
+ +| | | +|--------------|-----------------------------------------| +| Copyright | Copyright (C) 2006-2024 John MacFarlane | +| License | GNU GPL, version 2 or above | +| Maintainer | John MacFarlane \ | +| Stability | alpha | +| Portability | portable | +| Safe Haskell | Safe-Inferred | +| Language | GHC2021 | + +P2PRC + +
+ +
+ +Description + +
+ +This helper module exports the main writers, readers, and data structure +definitions from the Pandoc libraries. + +A typical application will chain together a reader and a writer to +convert strings from one format to another. For example, the following +simple program will act as a filter converting markdown fragments to +reStructuredText, using reference-style links instead of inline links: + + module Main where + import Text.Pandoc + import Data.Text (Text) + import qualified Data.Text.IO as T + + mdToRST :: Text -> IO Text + mdToRST txt = runIOorExplode $ + readMarkdown def txt + >>= writeRST def{ writerReferenceLinks = True } + + main :: IO () + main = do + T.getContents >>= mdToRST >>= T.putStrLn + +
+ +
+ +
+ +Synopsis + +- [runP2PRC](#v:runP2PRC) :: + [MapPortRequest](P2PRC.html#t:MapPortRequest "P2PRC") -\> + [IO]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/System-IO.html#t:IO "System.IO") + () +- data [MapPortRequest](#t:MapPortRequest) + = [MkMapPortRequest](#v:MkMapPortRequest) + [Int]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-Int.html#t:Int "Data.Int") + [String]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-String.html#t:String "Data.String") + +
+ +
+ +# Documentation + +
+ +runP2PRC :: +[MapPortRequest](P2PRC.html#t:MapPortRequest "P2PRC") -\> +[IO]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/System-IO.html#t:IO "System.IO") +() # + +
+ +Hello World + +
+ +
+ +
+ +data MapPortRequest +# + +
+ +Constructors + +| | | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----| +| MkMapPortRequest [Int]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-Int.html#t:Int "Data.Int") [String]($%7Bpkgroot%7D/../../../../dcnyq1a8qi8x59n5p53d0dx42cl8hf8x-ghc-9.6.5-doc/share/doc/ghc/html/libraries/base-4.18.2.1/Data-String.html#t:String "Data.String") |   | + +
+ +
+ +
+ +
+ + diff --git a/haskell/cabal.project b/haskell/cabal.project new file mode 100644 index 0000000..3293760 --- /dev/null +++ b/haskell/cabal.project @@ -0,0 +1,4 @@ + +packages: ./p2prc.cabal + +index-state: 2024-11-09T17:56:52Z diff --git a/haskell/dev_run.sh b/haskell/dev_run.sh new file mode 100755 index 0000000..949f6cc --- /dev/null +++ b/haskell/dev_run.sh @@ -0,0 +1,3 @@ +rm -rf *pem client/ plugin/ server/ p2p/ p2prc.[pP]* config.json dist-newstyle/ &&\ + cabal clean &&\ + cabal run diff --git a/haskell/gen_docs.sh b/haskell/gen_docs.sh new file mode 100755 index 0000000..d732d17 --- /dev/null +++ b/haskell/gen_docs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + + +cabal haddock + + +rm -rf ../Docs/haskell + +cp -r ./dist-newstyle/build/x86_64-linux/ghc-9.6.5/p2prc-0.1.0.0/doc/html/p2prc/ ../Docs/haskell diff --git a/haskell/lib/API.hs b/haskell/lib/API.hs new file mode 100644 index 0000000..e949f11 --- /dev/null +++ b/haskell/lib/API.hs @@ -0,0 +1,151 @@ + +module API + ( P2PRCapi(..) + , MapPortRequest(..) + , getP2prcAPI + ) + where + +import System.Process ( ProcessHandle ) + +import Data.Aeson ( FromJSON ) + +import Error + ( IOEitherError + ) + +import JSON + ( IPAddressTable(..) + , MapPortResponse(..) + , P2prcConfig + ) + +import CLI + ( StdInput(..) + , CLIOpt(..) + , eitherErrDecode + , getP2PrcCmd + , eitherExecProcess + , eitherExecProcessParser + , spawnProcP2Prc + ) + +import Environment ( cleanEnvironment ) + + +-- | Lower level P2PRC Haskell api that exposes basic functionality necessary to joint the network. + +data P2PRCapi + = MkP2PRCapi + { startServer :: IOEitherError ProcessHandle + -- ^ Start server + , execInitConfig :: IOEitherError P2prcConfig + -- ^ Instantiate server configuration + , execListServers :: IOEitherError IPAddressTable + -- ^ List servers in network + , execMapPort :: MapPortRequest -> IOEitherError MapPortResponse + -- ^ Exposes and associates a local TCP port with a remote DNS address + } + + +-- | This defines the request required to create an association between a TCP socket port and a DNS server in the network. If successful, it makes a resource available in the network. +data MapPortRequest = + MkMapPortRequest -- ^ P2PRC's port allocation request value + Int -- ^ TCP socket number + String -- ^ Network domain name + + +{-| + This function cleans the previous running state (ensuring a pure P2PRC runtime state) and builds up a conditional 'P2PRCapi' instance. + + ==== __Example__ + + The following example show how this function can be used to expose the runtime functionalities: + + @ + example :: IOEitherError P2PRCapi + example = do + + eitherP2prcAPI <- getP2prcAPI + + case eitherP2prcAPI of + ( Right + ( MkP2PRCapi + { startServer = startServer + , execInitConfig = execInitConfig + , execListServers = execListServers + , execMapPort = execMapPort + } + )) -> do + + -- Your code logic + + errValue -> errValue + @ +-} +{-# WARNING getP2prcAPI "This function is currently unstable since it assumes that the Haskell program is executed from the P2PRC \"haskell\" subfolder and the \"p2prc\" executable in the the root folder." #-} +getP2prcAPI :: IOEitherError P2PRCapi +getP2prcAPI = do + + cleanEnvironment + + eitherP2prcCmd <- getP2PrcCmd + + pure $ case eitherP2prcCmd of + (Right p2prcCmd) -> let + + execProcP2PrcParser :: + FromJSON a => + [CLIOpt] -> StdInput -> IOEitherError a + execProcP2PrcParser = eitherExecProcessParser p2prcCmd + -- TODO: GHC question, why does it scope down instead staying generic + + execProcP2Prc = eitherExecProcess p2prcCmd + + in do + + Right $ MkP2PRCapi + { startServer = spawnProcP2Prc p2prcCmd [ MkOptAtomic "--s" ] + + , execListServers = + execProcP2PrcParser [ MkOptAtomic "--ls" ] MkEmptyStdInput + + , execMapPort = + \ (MkMapPortRequest portNumber domainName) -> + execProcP2PrcParser + [ MkOptTuple + ( "--mp" + , show portNumber + ) + , MkOptTuple + ( "--dn" + , domainName + ) + ] + MkEmptyStdInput + + , execInitConfig = do + + confInitRes <- execProcP2Prc [ MkOptAtomic "--dc" ] MkEmptyStdInput + + case confInitRes of + (Right _) -> do + + -- TODO: get config file name dynamically + + -- TODO: change values before loading file + let fname = "/home/xecarlox/Desktop/p2p-rendering-computation/haskell/config.json" :: FilePath + + + -- TODO: read config check if file exists + configContent <- readFile fname + + pure $ eitherErrDecode configContent + + (Left err) -> pure $ Left err + + } + + (Left err) -> Left err + + diff --git a/haskell/lib/CLI.hs b/haskell/lib/CLI.hs new file mode 100644 index 0000000..6a1afc2 --- /dev/null +++ b/haskell/lib/CLI.hs @@ -0,0 +1,140 @@ +module CLI where + +import System.Process + ( readProcessWithExitCode + , proc + , createProcess + , terminateProcess + , ProcessHandle + ) + +import System.Exit + ( ExitCode(ExitFailure) + ) + +import Error + ( IOEitherError + , Error(..) + , assignError + ) + +import Data.Aeson + ( FromJSON + , eitherDecode + ) + +import qualified Data.Text as T + +import qualified Data.ByteString.Lazy.Char8 as LBC8 + + + +data StdInput + = MkEmptyStdInput + | MkStdInputVal String + + +instance Show StdInput where + show MkEmptyStdInput = "" + show (MkStdInputVal v) = v + + +data CLIOpt + = MkEmptyOpts + | MkOptAtomic String + | MkOptTuple (String, String) + +type CLIOptsInput = [String] +type CLICmd = String + + +eitherExecProcessParser :: + FromJSON a => + CLICmd -> [CLIOpt] -> StdInput -> IOEitherError a +eitherExecProcessParser p2prcCmd opts stdInput = + do + val <- eitherExecProcess p2prcCmd opts stdInput + + pure $ case val of + (Right v) -> eitherErrDecode v + (Left e) -> Left e + + +eitherErrDecode :: + FromJSON a => + String -> Either Error a +eitherErrDecode = eitherErrorDecode . eitherDecode . LBC8.pack + + +eitherExecProcess :: CLICmd -> [CLIOpt] -> StdInput -> IOEitherError String +eitherExecProcess cmd opts input = + do + (code, out, err) <- + readProcessWithExitCode + cmd + (optsToCLI opts) + (show input) + + pure $ case code of + ExitFailure i -> Left $ MkCLISystemError i cmd err + _ -> Right out + + +optsToCLI :: [CLIOpt] -> CLIOptsInput +optsToCLI = concatMap _optToCLI + where + + _optToCLI :: CLIOpt -> CLIOptsInput + _optToCLI MkEmptyOpts = [] + _optToCLI (MkOptAtomic o) = [o] + _optToCLI (MkOptTuple (o, v)) = [o, v] + + +spawnProcP2Prc :: CLICmd -> [CLIOpt] -> IOEitherError ProcessHandle +spawnProcP2Prc cmd opts = + do + let prc = proc cmd $ optsToCLI opts + + creationResult <- createProcess prc + + let (_, _, _, ph) = creationResult + + case creationResult of + (_, _, Just _, _) -> do + + terminateProcess ph + + pure $ Left $ MkErrorSpawningProcess cmd + + _-> pure $ Right ph + + +eitherErrorDecode :: Either String a -> Either Error a +eitherErrorDecode esa = + case esa of + (Left s) -> Left $ assignError s + (Right v) -> Right v + + +getP2PrcCmd :: IOEitherError String +getP2PrcCmd = do + + -- assumes the program is ran inside the haskell module in p2prc's repo + -- assumes that last path segment is "haskell" and that p2prc binary's name is "p2p-rendering-computation" + + let trimString = T.unpack . T.strip . T.pack + + eitherErrPwd <- eitherExecProcess "pwd" [MkEmptyOpts] MkEmptyStdInput + + case eitherErrPwd of + + (Right pwdOut) -> + eitherExecProcess + "sed" + [ MkOptAtomic "s/haskell/p2p-rendering-computation/" ] + $ MkStdInputVal + $ trimString pwdOut + + err -> pure err + + diff --git a/haskell/lib/Engine.hs b/haskell/lib/Engine.hs new file mode 100644 index 0000000..15838df --- /dev/null +++ b/haskell/lib/Engine.hs @@ -0,0 +1,160 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Engine + ( runP2PRC + ) + where + + +import Control.Concurrent +import Data.Char (toLower) +import System.IO +import Control.Monad (when) + + +import System.Process ( terminateProcess ) + + +import API + ( P2PRCapi(..) + , MapPortRequest(..) + , getP2prcAPI + ) + + +-- URGENT TASKS +-- +-- +-- TODO: add Haddock documentation +-- +-- TODO: P2PRC setup + -- check version P2PRC: only run if version if above a certain value + -- setup p2prc command + -- check if p2prc command is available in environment first + -- otherwise check folder above +-- +-- TODO: Fix API TODOS +-- +-- TODO: Fix JSON TODOS +-- +-- create DSL to start and orchestrate network +-- +-- TODO: publish haskell library +-- + +{-| + This function starts and bootstraps the P2PRC runtime that associates the a specific host's machine port to a DNS address to expose a certain application to the P2PRC network. You will only need to also import the 'MkMapPortRequest' data constructor to represent the this port request. + + ==== __Example__ + + This example demonstrates how it can be ran on the IO context: + + @ + example :: IO () + example = do + runP2PRC + ( MkMapPortRequest 8080 "jose.akilan.io" + ) + @ +-} +runP2PRC + :: MapPortRequest -- ^ TCP Port Request + -> IO () +runP2PRC (MkMapPortRequest portNumber domainName) = do + + -- + -- TODO: add quickcheck testing (quickchecking-dynamic) + -- + -- TODO: Change Standard Library + -- TODO: add GDTA syntax to data types + -- + -- TODO: need monad transformers to refactor the code + -- + -- + -- TODO: parse IO arguments; + -- TODO: create DSL from the standard input + -- + -- TODO: add use case examples (extra-source_files) + -- + -- TODO: setup nix flake package + -- Nix p2prc runtime packaging + -- Perhaps create internal script to run P2PRC from nix flake + -- "nix flake run ..." + -- simplify packaging + -- + -- Extra: + -- + -- TODO: Error + -- assign error: should parse other error + + + + eitherP2prcAPI <- getP2prcAPI + + case eitherP2prcAPI of + (Right + ( MkP2PRCapi + { startServer = startServer + , execInitConfig = execInitConfig + , execListServers = execListServers + , execMapPort = execMapPort + } + )) -> do + let + + + configValue <- execInitConfig + + -- TODO: get name of host server from config json + + print configValue + putStrLn "\n\n\n" + + eitherStartProcessHandle <- startServer + + + case eitherStartProcessHandle of + (Right startProcessHandle) -> do + + let sleepNSecs i = threadDelay (i * 1000000) + + sleepNSecs 5 + + outputStr <- execListServers + print outputStr + + mapPortOut <- execMapPort $ MkMapPortRequest portNumber domainName + + case mapPortOut of + (Right v) -> print v + (Left e) -> print e + + + -- TODO: work on looping function + -- + -- Loop (Run replica of haskell program on different $NODES) + -- - Start server + -- - wait 4 seconds + -- - Identify new node running p2prc with SSH external port exposed + -- - SSH into machine + -- - Use simple File transfer to setup files + -- - Run server + -- - Use remote machine p2prc cmd to map a port using --mp + -- - Return back the exposed public IP and port number back to stdout + + exitOnQ $ terminateProcess startProcessHandle + + + (Left err) -> print err + + (Left err) -> print err + + where + + exitOnQ :: IO () -> IO () + exitOnQ exitF = do + hSetBuffering stdin NoBuffering + c <- getChar + when (toLower c /= 'q') $ exitOnQ exitF + exitF + diff --git a/haskell/lib/Environment.hs b/haskell/lib/Environment.hs new file mode 100644 index 0000000..88edd1b --- /dev/null +++ b/haskell/lib/Environment.hs @@ -0,0 +1,57 @@ +module Environment + ( cleanEnvironment + ) + where + + +import System.Directory + ( doesDirectoryExist + , doesFileExist + , removeDirectoryRecursive + , removeFile + ) + +import Control.Monad ( when ) + + + +cleanEnvironment :: IO () +cleanEnvironment = do + + mapM_ removeFileIfExists + [ "cert.pem" + , "config.json" + , "key.pem" + , "p2prc.privateKey" + , "p2prc.PublicKeyBareMetal" + ] + + mapM_ removeDirRIfExists + [ "client" + , "p2p" + , "plugin" + , "server" + ] + + where + + removeIfExists + :: (FilePath -> IO Bool) + -> (FilePath -> IO ()) + -> FilePath + -> IO () + removeIfExists doesItExists rmIt filePath = + do + res <- doesItExists filePath + when res $ rmIt filePath + + removeDirRIfExists = + removeIfExists + doesDirectoryExist + removeDirectoryRecursive + + removeFileIfExists = + removeIfExists + doesFileExist + removeFile + diff --git a/haskell/lib/Error.hs b/haskell/lib/Error.hs new file mode 100644 index 0000000..2857fab --- /dev/null +++ b/haskell/lib/Error.hs @@ -0,0 +1,36 @@ +module Error + ( Error(..) + , IOEitherError + , assignError + ) where + + +{- | + Haskell-side Error value. This type is designed to parse and track System and P2PRC's error signals in a safe and effective manner. + + It does have an 'MkUnknownError' value which is meant to be warn about new kinds of error not yet accounted in this client. Github issues and pull requests are very welcome to improve error handling by parsing more types of errors. + +-} +data Error + = MkCLISystemError -- ^ This is a CLI System Error + Int -- ^ System error code + String -- ^ Command name executed + String -- ^ Error output + | MkErrorSpawningProcess -- ^ Spawing process error + String -- ^ Spawning executable name + | MkUnknownError -- ^ This is an unparsed P2PRC's error + String -- ^ Unparsed error message + deriving Show + +-- | Type synonym for an IO action with either returns an Error or a parsed value +type IOEitherError a = IO ( Either Error a) + +assignError :: String -> Error +assignError = MkUnknownError +-- +-- TODO: add megaparsec to parse Error Messages +-- +-- TODO: add error when internet connection is off +-- +-- Left (MkUnknownError "Unexpected end-of-input, expecting JSON value") +-- MkSystemError 1 "/home/xecarlox/Desktop/p2p-rendering-computation/p2p-rendering-computation" "2024/11/09 21:08:06 Get \"http://0.0.0.0:8088/MAPPort?port=3333&domain_name=\": dial tcp 0.0.0.0:8088: connect: connection refused\n" diff --git a/haskell/lib/JSON.hs b/haskell/lib/JSON.hs new file mode 100644 index 0000000..9affc8c --- /dev/null +++ b/haskell/lib/JSON.hs @@ -0,0 +1,202 @@ +{-# LANGUAGE OverloadedStrings #-} + +module JSON + ( P2prcConfig(..) + , IPAddressTable(..) + , IPAddress(..) + , ServerInfo(..) + , MapPortResponse(..) + ) + where + + +import Control.Monad ( mzero ) + +import qualified Data.Text as T + +import Data.Aeson + + +{-# WARNING MapPortResponse "This newtype is unstable at the moment due to the P2PRC's library error handling bug. For more information visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114#issuecomment-2474737015" #-} + +-- ^ This represents P2PRC's response to the TCP port and DNS address allocation. This value will confirm the successful allocation and return information about it. +newtype MapPortResponse + = MkMapPortResponse -- ^ Allocation information value + String -- ^ Column separated Host's IP address and Port String + deriving Show + + + +instance FromJSON MapPortResponse where + parseJSON (Object o) = do + + ipAddress <- o .: "IPAddress" + + pure $ MkMapPortResponse ipAddress + + parseJSON _ = mzero + + + +{-# WARNING P2prcConfig "This type is unstable at the moment due to the P2PRC's library error handling bug. For more information visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114#issuecomment-2474737015" #-} + +-- | This represents the server configuration that defines its attributes and behaviours in the network, as well as, the location of the runtime persistence artifacts. +newtype P2prcConfig + = MkP2prConfig + { machineName :: String -- ^ Machine Name + } deriving Show + + -- , hostServerPort :: Int + -- , iPV6Address :: Maybe String + -- , proxyPort :: Maybe Int + -- , fRPServerPort :: Bool + -- , behindNAT :: Bool + -- , iPTableKey :: String + -- , bareMetal :: Bool + -- , customConfig :: String + -- + -- , iPTable :: String -- File + -- , dockerContainers :: String -- Directory + -- , defaultDockerFile :: String -- Directory + -- , dockerRunLogs :: String -- Directory + -- , speedTestFile :: String -- File + -- , pluginPath :: String -- Directory + -- , trackContainersPath :: String -- File + -- , publicKeyFile :: String -- File + -- , privateKeyFile :: String -- File + -- , pemFile :: String -- File + -- , keyFile :: String -- File + -- , groupTrackContainersPath :: String -- File + + +-- TODO: p2prc API + -- + -- ListServers + -- remove "ip_address" root field if not needed + -- "Nat field" returning a JSON Boolean + -- serverPort as a JSON number + -- baremetalPort as a JSON number + -- have either IPV4 or IPV6 field visible + -- remove "customInformation" if not needed anymore + -- remove "escapeImplementation" if not needed anymore + -- + -- Config file + -- + -- Fix JSON number: ServerPort + -- Fix: IPV6Address dont show if value does not exist + -- Fix JSON number: ProxyPort to number (dont show if it does not exist) + -- Fix JSON number: fRPServerPort + -- Fix JSON boolean: fRPServerPort + -- Fix JSON boolean: behindNAT + -- Fix JSON boolean: bareMetal + -- remove "customConfig" if not needed + -- + -- MapPort + -- to have a dedicated ip address (with type either IPV6 or IPV4 fields) + -- to have a dedicated port field + + +instance FromJSON P2prcConfig where + parseJSON (Object o) = do + + machineName <- o .: "MachineName" + + pure + $ MkP2prConfig + { machineName=machineName + } + + parseJSON _ = mzero + + +{-# WARNING IPAddressTable "This newtype is highly unstable due to undergoing work on improving P2PRC's server api. For more information, visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114" #-} + +-- | This is a wrapper value that parses a json key value from the list of ip addresses in the network. +newtype IPAddressTable + = MkIPAddressTable -- ^ Wrapping constructor + [ServerInfo] -- ^ List current network servers + deriving Show + + +instance FromJSON IPAddressTable where + parseJSON = withObject "IPAdressTable" $ + \ v -> + MkIPAddressTable <$> v .: "ip_address" + + +{-# WARNING ServerInfo "This type is highly unstable due to undergoing work on improving P2PRC's server api. For more information, visit: https://github.com/Akilan1999/p2p-rendering-computation/issues/114" #-} +{- | + This is a record that keeps track of the current state of every node in the network. It is crucial information required for orchestration strategies. +-} +data ServerInfo = + MkServerInfo + { name :: T.Text -- ^ Machine name + , ip :: IPAddress -- ^ Machine IP address + , latency :: Int -- ^ Response latency + , download :: Int -- ^ Download speed + , upload :: Int -- ^ Upload speed + , serverPort :: Int -- ^ Server port number + , bareMetalSSHPort :: Maybe Int -- ^ SSH machine port number + , nat :: Bool -- ^ Checking if node is behind a NAT + , escapeImplementation :: Maybe T.Text -- ^ Type of NAT trasversal technique + , customInformation :: Maybe T.Text -- ^ Custom String information + } + deriving Show + +-- | This is a simple representation of the IP address of nodes in the network. +data IPAddress + = MkIPv4 String -- ^ IP version 4 address + | MkIPv6 String -- ^ IP version 6 address + deriving Show + + +instance FromJSON ServerInfo where + parseJSON = withObject "ServerInfo" $ + \ o -> do + + name <- o .: "Name" + ip4str <- o .: "IPV4" + ip6str <- o .: "IPV6" + latency <- o .: "Latency" + download <- o .: "Download" + upload <- o .: "Upload" + serverPort <- o .: "ServerPort" + bmSshPort <- o .: "BareMetalSSHPort" + nat <- o .: "NAT" + mEscImpl <- o .: "EscapeImplementation" + custInfo <- o .: "CustomInformation" + + + pure $ + MkServerInfo + { name = name + , ip = getIPAddress ip4str ip6str + , latency = latency + , download = download + , upload = upload + , serverPort = getPortUNSAFE serverPort + , bareMetalSSHPort = getBMShhPort bmSshPort + , nat = getNat nat + , escapeImplementation = mEscImpl + , customInformation = custInfo -- TODO: deal with null value + } + + where + + getNat :: String -> Bool -- TODO: Change it to normal JSON + getNat ('T':_) = True + getNat _ = False + + getBMShhPort :: String -> Maybe Int -- TODO: Dangerous partial function call !!!!!!!!!!!!!!!!!!! + getBMShhPort [] = Nothing + getBMShhPort bmSshPort = Just $ getPortUNSAFE bmSshPort + + getPortUNSAFE :: String -> Int -- TODO: Dangerous partial function call !!!!!!!!!!!!!!!!!!! + getPortUNSAFE = read + + getIPAddress :: String -> String -> IPAddress + getIPAddress [] ip6 = MkIPv6 ip6 + getIPAddress ip4 _ = MkIPv4 ip4 + + + diff --git a/haskell/lib/P2PRC.hs b/haskell/lib/P2PRC.hs new file mode 100644 index 0000000..72e3f11 --- /dev/null +++ b/haskell/lib/P2PRC.hs @@ -0,0 +1,94 @@ +{-# OPTIONS_HADDOCK show-extensions #-} + + +{- | + Module : P2PRC + Copyright : Copyright (C) 2024-2024 Jose Fernandes + License : GNU GPL, version 2 or above + Maintainer : Jose Fernandes + Stability : beta + Portability : portable + +This library provides an interface to the P2Prc runtime. + +This Module intends to export the main functions and data type definitions necessary to get started with the P2PRC api. + +A minimal application will require the import of 'runP2PRC' function that accepts a 'MapPortRequest' value that exposes a specific port number and associates it with a domain name in the internet. + +This is a small template to get quickly get started with this interface. We assume the user has already an application listening on the tcp socket "8080". + +> module Main where +> +> import P2PRC +> ( runP2PRC +> , MapPortRequest(MkMapPortRequest) +> ) +> +> main :: IO () +> main = +> runP2PRC +> ( MkMapPortRequest 8080 "jose.akilan.io" +> ) + +-} + + +module P2PRC + ( + -- * Functions + {- | These are the available functions available to interact with the P2Prc environment, at a lower level of abstraction. + It is intended this way to give freedom to the developer to implement their own orchestration strategies. + -} + runP2PRC + , getP2prcAPI + + -- * Data Types + {- | This section describes and explains the library's type system, more specifically, the interfaces and primitive types. + -} + + -- ** Interface data types + -- | This section gives an overview on the runtime and host machine interfaces. + , P2PRCapi(..) + , P2prcConfig(..) + , MapPortRequest(..) + , MapPortResponse(..) + + -- ** Primitive data types + -- | These types represent the core data that is communicated between requests and the runtime. + , IPAddressTable(..) + , ServerInfo(..) + , IPAddress(..) + , Error(..) + + -- ** Type Synonyms + -- | This section is reserved to some useful type synonyms that add significant ergonomics. + , IOEitherError + ) + where + + +import Engine + ( runP2PRC + ) + +import JSON + ( IPAddressTable(..) + , ServerInfo(..) + , IPAddress(..) + , MapPortResponse(..) + , P2prcConfig(..) + ) + +import API + ( MapPortRequest(..) + , P2PRCapi(..) + , getP2prcAPI + ) + + +import Error + ( Error(..) + , IOEitherError + ) + + diff --git a/haskell/p2prc.cabal b/haskell/p2prc.cabal new file mode 100644 index 0000000..55af357 --- /dev/null +++ b/haskell/p2prc.cabal @@ -0,0 +1,67 @@ +cabal-version: 3.0 + +name: p2prc + +-- See the Haskell package versioning policy (PVP) for standards +-- guiding when and how versions should be incremented. +-- https://pvp.haskell.org +-- PVP summary: +-+------- breaking API changes +-- | | +----- non-breaking API additions +-- | | | +--- code changes with no API change +version: 0.1.0.0 + +synopsis: P2PRC haskell library + +description: Implements a client interface to the P2PRC networking runtime + +author: xecarlox94 + +maintainer: jf94.uk@gmail.com + +category: Network +build-type: Simple + +extra-doc-files: README.md + + +common warnings + ghc-options: -Wall + +executable p2prc + + import: warnings + + hs-source-dirs: src + + main-is: Main.hs + + other-extensions: OverloadedStrings + + build-depends: base + , p2prc + + default-language: GHC2021 + +library + + import: warnings + + build-depends: base + , text + , aeson + , process + , bytestring + , directory + + hs-source-dirs: lib + + exposed-modules: P2PRC + + other-modules: API + , Engine + , CLI + , Environment + , JSON + , Error + + default-language: GHC2021 diff --git a/haskell/src/Main.hs b/haskell/src/Main.hs new file mode 100644 index 0000000..97d8636 --- /dev/null +++ b/haskell/src/Main.hs @@ -0,0 +1,12 @@ +module Main where + +import P2PRC + ( runP2PRC + , MapPortRequest(MkMapPortRequest) + ) + +main :: IO () +main = + runP2PRC + ( MkMapPortRequest 8080 "jose.akilan.io" + )