Meta: Add files to repository

This commit is contained in:
Alec Murphy 2025-02-16 15:21:19 -05:00
parent 6d27d43268
commit 52cb92f587
120 changed files with 71820 additions and 0 deletions

274
Slon/Api/V1/Accounts.HC Normal file
View file

@ -0,0 +1,274 @@
U0 @slon_api_v1_accounts_get(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
U8* path = @slon_strnew(session, @slon_http_request_path(session));
I64 path_segments_count = 0;
U8** path_segments = String.Split(path, '/', &path_segments_count);
JsonObject* acct = NULL;
if (!StrICmp("verify_credentials", path_segments[3])) {
if (@slon_api_authorized(session)) {
SLON_AUTH_ACCOUNT_ID
acct = @slon_api_account_by_id(account_id);
if (acct) {
@slon_http_send_json(session, acct);
} else {
@slon_http_set_status_code(session, 404);
}
} else {
@slon_http_set_status_code(session, 401);
}
} else {
// Work with account :id
U8* some_account_id = path_segments[3];
acct = @slon_api_account_by_id(some_account_id);
if (!acct) {
@slon_http_set_status_code(session, 404);
goto slon_api_v1_accounts_get_return;
}
if (path_segments_count > 5) {
U8* method = path_segments[4];
if (!StrICmp("following", method)) {
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
goto slon_api_v1_accounts_get_return;
}
if (!StrICmp("statuses", method)) {
// Return the Account's Statuses
JsonArray* status_array = db->o("statuses")->a(some_account_id);
I64 count = 0;
// FILTERS
I64 limit = 20; // default
U64 max_id = 0;
U64 min_id = 0;
Bool only_media = request_json->@("only_media");
Bool exclude_replies = request_json->@("exclude_replies");
Bool exclude_reblogs = request_json->@("exclude_reblogs");
no_warn exclude_reblogs;
Bool pinned = request_json->@("pinned");
// FIXME: Implement "only_media", "exclude_reblogs", "tagged"
Bool exclude_status = FALSE;
U64 status_id = 0;
if (StrLen(request_json->@("limit")) > 0) {
// 40 = maximum per https://docs.joinmastodon.org/methods/accounts/#statuses
limit = MinI64(40, Str2I64(request_json->@("limit")));
}
if (StrLen(request_json->@("max_id")) > 0) {
max_id = Str2I64(request_json->@("max_id"));
}
if (StrLen(request_json->@("min_id")) > 0) {
min_id = Str2I64(request_json->@("min_id"));
}
JsonArray* statuses = Json.CreateArray();
JsonObject* status = NULL;
if (status_array && status_array->length) {
I64 i;
for (i = 0; i < status_array->length; i++) {
status = status_array->o(i);
status_id = Str2I64(status->@("id"));
exclude_status = FALSE;
if (status->@("deleted")) {
exclude_status = TRUE;
}
if (max_id > 0 && status_id >= max_id) {
exclude_status = TRUE;
}
if (min_id > 0 && status_id <= min_id) {
exclude_status = TRUE;
}
if (only_media && !Json.Get(status, "media_attachments")(JsonArray*)->length) {
exclude_status = TRUE;
}
if (exclude_replies && StrLen(status->@("in_reply_to_account_id")) > 0 && StrICmp(account_id, status->@("in_reply_to_account_id"))) {
exclude_status = TRUE;
}
if (pinned && !status->@("pinned")) {
exclude_status = TRUE;
}
if (!exclude_status) {
statuses->append(Json.CreateItem(status, JSON_OBJECT));
count++;
}
if (limit > 0 && count >= limit) {
break;
}
}
}
@slon_http_send_json(session, statuses);
Json.Delete(statuses);
goto slon_api_v1_accounts_get_return;
}
@slon_http_set_status_code(session, 404);
} else {
// Return the Account profile
JsonObject* profile_object = Json.Clone(acct);
profile_object->unset("source");
@slon_http_send_json(session, profile_object);
Json.Delete(profile_object);
}
}
slon_api_v1_accounts_get_return:
@slon_free(session, path);
}
Bool @slon_api_v1_accounts_key_is_boolean(U8* name)
{
return (!StrICmp(name, "locked") || !StrICmp(name, "bot") || !StrICmp(name, "discoverable") || !StrICmp(name, "hide_collections") || !StrICmp(name, "indexable"));
}
U0 @slon_api_v1_accounts_patch(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
U8* path = @slon_strnew(session, @slon_http_request_path(session));
I64 path_segments_count = 0;
U8** path_segments = String.Split(path, '/', &path_segments_count);
JsonObject* acct = NULL;
if (!StrICmp("update_credentials", path_segments[3])) {
if (@slon_api_authorized(session)) {
SLON_AUTH_ACCOUNT_ID
if (!request_json || !request_json->keys) {
@slon_http_set_status_code(session, 400);
goto slon_api_v1_accounts_patch_return;
}
// FIXME: Support avatars/banners
acct = @slon_api_account_by_id(account_id);
if (!acct) {
@slon_http_set_status_code(session, 404);
goto slon_api_v1_accounts_patch_return;
}
JsonObject* source = acct->@("source");
I64 fields_attributes_indexes[16];
I64 fields_attributes_count = 0;
U8* field_name;
U8* field_value;
JsonKey* update_field_index;
JsonObject* field_object;
Bool update_fields_from_form_data = FALSE;
Bool integer_is_in_index = FALSE;
I64 i;
I64 index;
MemSet(fields_attributes_indexes, NULL, sizeof(I64) * 16);
JsonArray* fields_array = Json.CreateArray();
JsonKey* key = request_json->keys;
while (key) {
if (!String.BeginsWith("fields_attributes", key->name) && !String.BeginsWith("source", key->name)) {
if (@slon_api_v1_accounts_key_is_boolean(key->name)) {
switch (key->type) {
case JSON_STRING:
acct->set(key->name, @slon_api_boolean_from_string(key->value), JSON_BOOLEAN);
break;
default:
acct->set(key->name, key->value > 0, JSON_BOOLEAN);
break;
}
} else {
acct->set(key->name, key->value, key->type);
}
} else if (String.BeginsWith("source", key->name)) {
if (!StrICmp("source[language]", key->name)) {
source->set("language", key->value);
}
if (!StrICmp("source[privacy]", key->name)) {
source->set("privacy", key->value);
}
} else if (String.BeginsWith("fields_attributes[", key->name)) {
// Get fields indexes from form data
update_fields_from_form_data = TRUE;
index = Str2I64(key->name + StrLen("fields_attributes["));
if (!fields_attributes_count) {
fields_attributes_indexes[fields_attributes_count] = index;
++fields_attributes_count;
} else {
integer_is_in_index = FALSE;
i = 0;
while (i < fields_attributes_count) {
if (index == fields_attributes_indexes[i])
integer_is_in_index = TRUE;
++i;
}
if (!integer_is_in_index) {
fields_attributes_indexes[fields_attributes_count] = index;
++fields_attributes_count;
}
}
} else if (!StrICmp("fields_attributes", key->name)) {
// Get fields data from JSON object
AdamLog("let's get fields data from JSON object!!\n");
update_field_index = key->value(JsonObject*)->keys;
while (update_field_index) {
field_object = update_field_index->value;
field_object->set("verified_at", NULL, JSON_NULL);
AdamLog("before stringify\n");
AdamLog("%s\n", Json.Stringify(field_object));
AdamLog("after stringify\n");
fields_array->append(Json.CreateItem(field_object, JSON_OBJECT));
update_field_index = update_field_index->next;
}
}
key = key->next;
}
if (update_fields_from_form_data) {
for (i = 0; i < fields_attributes_count; i++) {
index = fields_attributes_indexes[i];
field_name = NULL;
field_value = NULL;
key = request_json->keys;
while (key) {
StrPrint(scratch_buffer, "fields_attributes[%d][name]", index);
if (String.BeginsWith(scratch_buffer, key->name)) {
field_name = key->value;
}
StrPrint(scratch_buffer, "fields_attributes[%d][value]", index);
if (String.BeginsWith(scratch_buffer, key->name)) {
field_value = key->value;
}
if (field_name && field_value) {
// create new field_object, and append to acct->fields
field_object = Json.CreateObject();
field_object->set("name", field_name, JSON_STRING);
field_object->set("value", field_value, JSON_STRING);
field_object->set("verified_at", NULL, JSON_NULL);
fields_array->append(Json.CreateItem(field_object, JSON_OBJECT));
field_name = NULL;
field_value = NULL;
}
key = key->next;
}
}
}
acct->set("fields", fields_array, JSON_ARRAY);
source->set("fields", acct->@("fields"), JSON_ARRAY);
@slon_db_save_accounts_to_disk;
@slon_db_actors_update_user(acct);
@slon_http_send_json(session, acct);
} else {
@slon_http_set_status_code(session, 401);
}
} else {
@slon_http_set_status_code(session, 404);
}
slon_api_v1_accounts_patch_return:
@slon_free(session, path);
}

73
Slon/Api/V1/Apps.HC Normal file
View file

@ -0,0 +1,73 @@
U8* @slon_api_v1_apps_generate_app_id(SlonHttpSession* session)
{
U8* app_id = @slon_calloc(session, 16);
I64 i;
for (i = 0; i < 6; i++) {
String.Append(app_id, "%d", RandU64 % 10);
}
return app_id;
}
U8* @slon_api_v1_apps_generate_client_id(SlonHttpSession* session)
{
U8* client_id = NULL;
Bool client_id_exists = TRUE;
while (client_id_exists) {
if (client_id)
@slon_free(session, client_id);
client_id = @slon_api_generate_random_hex_string(session, 16);
client_id_exists = db->o("apps")->@(client_id) > 0;
}
return client_id;
}
U8* @slon_api_v1_apps_generate_client_secret(SlonHttpSession* session)
{
return @slon_api_generate_random_hex_string(session, 32);
}
U0 @slon_api_v1_apps_post(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
U8* id = @slon_api_v1_apps_generate_app_id(session);
U8* client_id = @slon_api_v1_apps_generate_client_id(session);
U8* client_secret = @slon_api_v1_apps_generate_client_secret(session);
I64 request_scopes_count = 0;
U8** request_scopes = NULL;
if (StrFind("+", request_json->@("scopes")) > 0) {
request_scopes = String.Split(request_json->@("scopes"), '+', &request_scopes_count);
} else {
request_scopes = String.Split(request_json->@("scopes"), ' ', &request_scopes_count);
}
JsonArray* scopes = Json.CreateArray();
I64 i;
for (i = 0; i < request_scopes_count; i++) {
scopes->append(Json.CreateItem(request_scopes[i], JSON_STRING));
}
JsonArray* redirect_uris = Json.CreateArray();
redirect_uris->append(Json.CreateItem(request_json->@("redirect_uris"), JSON_STRING));
JsonObject* credential_app = Json.CreateObject();
credential_app->set("id", id, JSON_STRING);
credential_app->set("name", request_json->@("client_name"), JSON_STRING);
credential_app->set("website", request_json->@("website"), JSON_STRING);
credential_app->set("scopes", scopes, JSON_ARRAY);
credential_app->set("redirect_uris", redirect_uris, JSON_ARRAY);
credential_app->set("redirect_uri", request_json->@("redirect_uris"), JSON_STRING);
credential_app->set("client_id", client_id, JSON_STRING);
credential_app->set("client_secret", client_secret, JSON_STRING);
credential_app->set("client_secret_expires_at", "0", JSON_STRING);
db->o("apps")->set(client_id, credential_app, JSON_OBJECT);
@slon_db_save_apps_to_disk;
@slon_http_send_json(session, credential_app);
@slon_free(session, id);
@slon_free(session, client_id);
@slon_free(session, client_secret);
}

12
Slon/Api/V1/Blocks.HC Normal file
View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_blocks_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

12
Slon/Api/V1/Bookmarks.HC Normal file
View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_bookmarks_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_conversations_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_custom_emojis_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

12
Slon/Api/V1/Favourites.HC Normal file
View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_favourites_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

12
Slon/Api/V1/Filters.HC Normal file
View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_filters_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_follow_requests_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_followed_tags_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

View file

@ -0,0 +1,16 @@
U0 @slon_api_v1_notifications_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
if (String.EndsWith("policy", @slon_http_request_path(session))) {
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
} else {
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
}
} else {
@slon_http_set_status_code(session, 401);
}
}

144
Slon/Api/V1/Statuses.HC Normal file
View file

@ -0,0 +1,144 @@
U0 (*@slon_api_status_create_fedi)(JsonObject* status) = NULL;
U0 (*@slon_api_status_delete_fedi)(JsonObject* status) = NULL;
U0 @slon_api_v1_statuses_delete(SlonHttpSession* session)
{
if (@slon_api_authorized(session)) {
SLON_AUTH_ACCOUNT_ID
JsonArray* statuses = db->o("statuses")->a(account_id);
if (!statuses || !statuses->length) {
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
return;
}
U8* path = @slon_strnew(session, @slon_http_request_path(session));
I64 path_segments_count = 0;
U8** path_segments = String.Split(path, '/', &path_segments_count);
if (path_segments_count < 4) {
goto slon_api_v1_statuses_delete_return;
}
U8* id = path_segments[3];
JsonObject* status;
I64 i;
for (i = 0; i < statuses->length; i++) {
status = statuses->@(i);
if (!StrICmp(status->@("id"), id)) {
status->set("deleted", TRUE, JSON_BOOLEAN);
@slon_db_save_statuses_to_disk;
@slon_db_instance_decrement_status_count;
@slon_db_save_instance_to_disk;
if (@slon_api_status_delete_fedi) {
@slon_api_status_delete_fedi(Json.Clone(status));
}
goto slon_api_v1_statuses_delete_return;
}
}
slon_api_v1_statuses_delete_return:
Free(path_segments);
@slon_free(session, path);
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
} else {
@slon_http_set_status_code(session, 401);
}
}
U0 @slon_api_v1_statuses_post(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
SLON_AUTH_ACCOUNT_ID
Bool idempotency_key_already_seen = FALSE;
U8* idempotency_key = @slon_http_request_header(session, "idempotency-key");
if (StrLen(idempotency_key) > 0 && db->o("idempotency_keys")->@(idempotency_key)) {
idempotency_key_already_seen = TRUE;
}
if (!idempotency_key_already_seen) {
Json.Set(db->o("idempotency_keys"), idempotency_key, Now, JSON_NUMBER);
}
U8* id = @slon_api_generate_unique_id(session);
U8* created_at = @slon_api_timestamp_from_cdate(session, Now);
JsonObject* app_object = db->o("apps")->@(Json.Get(session->auth, "client_id"));
JsonObject* status_app = Json.CreateObject();
status_app->set("name", app_object->@("name"), JSON_STRING);
status_app->set("website", app_object->@("website"), JSON_STRING);
JsonObject* account_object = Json.Clone(@slon_api_account_by_id(account_id));
account_object->unset("source");
// U8* language = request_json->@("language");
U8* username = account_object->@("username");
Bool sensitive = request_json->@("sensitive") > 0;
U8* in_reply_to_id = request_json->@("in_reply_to_id");
U8* visibility = request_json->@("visibility");
if (!StrLen(visibility)) {
visibility = "public";
}
StrPrint(scratch_buffer, "https://%s/users/%s/statuses/%s", db->o("instance")->@("uri"), username, id);
U8* uri = @slon_strnew(session, scratch_buffer);
StrPrint(scratch_buffer, "https://%s/@%s/%s", db->o("instance")->@("uri"), username, id);
U8* url = @slon_strnew(session, scratch_buffer);
// Mona lets us post with: id, created_at, content, visibility, uri, url, account, application
// Mastodon iOS app lets us post with +: reblogs_count, favourites_count, emojis, tags, mentions
// IceCubesApp lets us post with +: media_attachments, replies_count, spoiler_text, sensitive
JsonObject* status = Json.CreateObject();
status->set("id", id, JSON_STRING);
status->set("created_at", created_at, JSON_STRING);
status->set("content", request_json->@("status"), JSON_STRING);
status->set("visibility", visibility, JSON_STRING);
status->set("uri", uri, JSON_STRING);
status->set("url", url, JSON_STRING);
status->set("account", account_object, JSON_OBJECT);
status->set("application", status_app, JSON_OBJECT);
status->set("reblogs_count", 0, JSON_NUMBER);
status->set("favourites_count", 0, JSON_NUMBER);
status->set("emojis", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
status->set("tags", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
status->set("mentions", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
status->set("media_attachments", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
status->set("replies_count", 0, JSON_NUMBER);
status->set("spoiler_text", "", JSON_STRING);
status->set("sensitive", sensitive, JSON_BOOLEAN);
if (StrLen(in_reply_to_id) > 0) {
status->set("in_reply_to_id", in_reply_to_id, JSON_STRING);
}
if (!idempotency_key_already_seen) {
db->o("statuses")->a(account_id)->append(Json.CreateItem(status, JSON_OBJECT));
@slon_db_save_statuses_to_disk;
@slon_db_instance_increment_status_count;
@slon_db_save_instance_to_disk;
if (@slon_api_status_create_fedi) {
@slon_api_status_create_fedi(Json.Clone(status));
}
}
@slon_http_send_json(session, status);
Json.Delete(status_app);
Json.Delete(account_object);
Json.Delete(app_object);
@slon_free(session, uri);
@slon_free(session, url);
@slon_free(session, id);
@slon_free(session, created_at);
} else {
@slon_http_set_status_code(session, 401);
}
}

12
Slon/Api/V1/Timelines.HC Normal file
View file

@ -0,0 +1,12 @@
U0 @slon_api_v1_timelines_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

12
Slon/Api/V2/Filters.HC Normal file
View file

@ -0,0 +1,12 @@
U0 @slon_api_v2_filters_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

9
Slon/Api/V2/Instance.HC Normal file
View file

@ -0,0 +1,9 @@
U0 @slon_api_v2_instance_get(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
StrPrint(scratch_buffer, "{\"domain\":\"%s\"}", db->o("instance")->@("uri"));
@slon_http_set_content_type(session, "application/json; charset=utf-8");
@slon_http_send_string(session, scratch_buffer);
}

View file

@ -0,0 +1,12 @@
U0 @slon_api_v2_suggestions_get(SlonHttpSession* session)
{
// SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (@slon_api_authorized(session)) {
// SLON_AUTH_ACCOUNT_ID
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_ARRAY);
} else {
@slon_http_set_status_code(session, 401);
}
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/api/v1/statuses", @slon_http_request_path(session))) {
@slon_api_v1_statuses_delete(session);
return;
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/api/v1/accounts", @slon_http_request_path(session))) {
@slon_api_v1_accounts_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/users/", @slon_http_request_path(session)) && String.EndsWith("json", @slon_http_request_header(session, "accept"))) {
@slon_activitypub_users_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/blocks", @slon_http_request_path(session))) {
@slon_api_v1_blocks_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/bookmarks", @slon_http_request_path(session))) {
@slon_api_v1_bookmarks_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/conversations", @slon_http_request_path(session))) {
@slon_api_v1_conversations_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/custom_emojis", @slon_http_request_path(session))) {
@slon_api_v1_custom_emojis_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/favourites", @slon_http_request_path(session))) {
@slon_api_v1_favourites_get(session);
return;
}

View file

@ -0,0 +1,9 @@
if (!StrICmp("/api/v1/filters", @slon_http_request_path(session))) {
@slon_api_v1_filters_get(session);
return;
}
if (!StrICmp("/api/v2/filters", @slon_http_request_path(session))) {
@slon_api_v2_filters_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/follow_requests", @slon_http_request_path(session))) {
@slon_api_v1_follow_requests_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/followed_tags", @slon_http_request_path(session))) {
@slon_api_v1_followed_tags_get(session);
return;
}

View file

@ -0,0 +1,9 @@
if (!StrICmp("/api/v1/instance", @slon_http_request_path(session))) {
@slon_http_send_json(session, db->o("instance"));
return;
}
if (!StrICmp("/api/v2/instance", @slon_http_request_path(session))) {
@slon_api_v2_instance_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/api/v1/notifications", @slon_http_request_path(session))) {
@slon_api_v1_notifications_get(session);
return;
}

View file

@ -0,0 +1,9 @@
if (!StrICmp("/oauth/authorize", @slon_http_request_path(session))) {
@slon_http_send_html_file(session, "M:/Slon/Static/oauth/authorize.html");
return;
}
if (!StrICmp("/oauth/verify_access", @slon_http_request_path(session))) {
@slon_oauth_verify_access_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v2/suggestions", @slon_http_request_path(session))) {
@slon_api_v2_suggestions_get(session);
return;
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/api/v1/timelines", @slon_http_request_path(session))) {
@slon_api_v1_timelines_get(session);
return;
}

28
Slon/Endpoints/Get/Web.HC Normal file
View file

@ -0,0 +1,28 @@
if (String.EndsWith(".css", @slon_http_request_path(session))) {
@slon_http_set_content_type(session, "text/css");
@slon_http_send_file(session, "M:/Slon/Static/css/main.css");
return;
}
if (!StrICmp("/js/header.js", @slon_http_request_path(session))) {
@slon_http_set_content_type(session, "text/javascript");
@slon_http_send_file(session, "M:/Slon/Static/js/header.js");
return;
}
if (String.EndsWith(".js", @slon_http_request_path(session))) {
@slon_http_set_content_type(session, "text/javascript");
@slon_http_send_file(session, "M:/Slon/Static/js/statuses.js");
return;
}
if (!StrICmp("/alec.png", @slon_http_request_path(session))) {
@slon_http_set_content_type(session, "image/png");
@slon_http_send_file(session, "A:/avatar-circle-4bpp.png");
return;
}
if (String.BeginsWith("/@", @slon_http_request_path(session))) {
@slon_web_user_get(session);
return;
}

View file

@ -0,0 +1,14 @@
if (!StrICmp("/.well-known/host-meta", @slon_http_request_path(session))) {
@slon_host_meta(session);
return;
}
if (!StrICmp("/.well-known/oauth-authorization-server", @slon_http_request_path(session))) {
@slon_oauth_well_known(session);
return;
}
if (!StrICmp("/.well-known/webfinger", @slon_http_request_path(session))) {
@slon_webfinger(session);
return;
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/api/v1/accounts", @slon_http_request_path(session))) {
@slon_api_v1_accounts_patch(session);
return;
}

View file

@ -0,0 +1,4 @@
if (String.BeginsWith("/users/", @slon_http_request_path(session))) {
@slon_activitypub_users_post(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/apps", @slon_http_request_path(session))) {
@slon_api_v1_apps_post(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/oauth/token", @slon_http_request_path(session))) {
@slon_oauth_token_post(session);
return;
}

View file

@ -0,0 +1,4 @@
if (!StrICmp("/api/v1/statuses", @slon_http_request_path(session))) {
@slon_api_v1_statuses_post(session);
return;
}

394
Slon/Http/AdminServer.HC Normal file
View file

@ -0,0 +1,394 @@
U0 @slon_admin_html_form_from_json_object(SlonHttpSession* session, U8* buf, JsonObject* o)
{
if (!session || !buf || !o)
return;
JsonKey* key = o->keys;
String.Append(buf, "<table>");
while (key) {
switch (key->type) {
case JSON_BOOLEAN:
case JSON_STRING:
case JSON_NUMBER:
String.Append(buf, "<tr><td><label>%s</label></td><td>", key->name);
break;
default:
break;
}
switch (key->type) {
case JSON_BOOLEAN:
String.Append(buf, "<input name=%s type=checkbox %s>", key->name, @t(key->value, "checked", ""));
break;
case JSON_STRING:
String.Append(buf, "<input name=%s type=text value=\"%s\" required>", key->name, key->value);
break;
case JSON_NUMBER:
String.Append(buf, "<input name=%s type=text value=\"%d\" required>", key->name, ToI64(key->value));
break;
default:
break;
}
String.Append(buf, "</td></tr>");
key = key->next;
}
String.Append(buf, "</table>");
}
U0 @slon_admin_create_ap_actor(SlonHttpSession* session, JsonObject* acct)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
JsonObject* actors = db->o("actors");
U8* domain = db->o("instance")->@("uri");
U8* username = acct->@("username");
JsonObject* actor = Json.Clone(SLON_DEFAULT_ACTOR_OBJECT);
StrPrint(scratch_buffer, "https://%s/users/%s", domain, username);
actor->set("id", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s/following", domain, username);
actor->set("following", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s/followers", domain, username);
actor->set("followers", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s/inbox", domain, username);
actor->set("inbox", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s/outbox", domain, username);
actor->set("outbox", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s/collections/featured", domain, username);
actor->set("featured", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s/collections/tags", domain, username);
actor->set("featuredTags", scratch_buffer, JSON_STRING);
actor->set("preferredUsername", username, JSON_STRING);
actor->set("name", acct->@("display_name"), JSON_STRING);
actor->set("summary", acct->@("note"), JSON_STRING);
JsonObject* icon = Json.Parse("{\"type\":\"Image\"}");
icon->set("url", acct->@("avatar"), JSON_STRING);
actor->set("icon", icon, JSON_OBJECT);
StrPrint(scratch_buffer, "https://%s/@%s", domain, username);
actor->set("url", scratch_buffer, JSON_STRING);
actor->set("published", acct->@("created_at"), JSON_STRING);
actor->set("attachment", acct->@("fields"), JSON_ARRAY);
actor->set("accountId", acct->@("id"), JSON_STRING);
db->o("private_keys")->set(username, request_json->@("privatekey"), JSON_STRING);
JsonObject* publickey = Json.CreateObject();
StrPrint(scratch_buffer, "https://%s/users/%s#main-key", domain, username);
publickey->set("id", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s", domain, username);
publickey->set("owner", scratch_buffer, JSON_STRING);
I64 x;
publickey->set("publicKeyPem", @base64_decode(request_json->@("publickey"), &x), JSON_STRING);
actor->set("publicKey", publickey, JSON_OBJECT);
actors->set(acct->@("username"), actor, JSON_OBJECT);
@slon_db_save_to_disk;
}
U0 @slon_admin_create_account(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
U8* id = @slon_api_generate_unique_id(session);
U8* created_at = @slon_api_timestamp_from_cdate(session, Now);
JsonObject* acct = Json.CreateObject();
JsonObject* source = Json.CreateObject();
acct->set("id", id, JSON_STRING);
acct->set("created_at", created_at, JSON_STRING);
acct->set("username", request_json->@("username"), JSON_STRING);
acct->set("acct", request_json->@("username"), JSON_STRING);
acct->set("display_name", request_json->@("display_name"), JSON_STRING);
acct->set("email", request_json->@("email"), JSON_STRING);
acct->set("note", request_json->@("bio"), JSON_STRING);
acct->set("avatar", request_json->@("avatar"), JSON_STRING);
acct->set("header", request_json->@("header"), JSON_STRING);
acct->set("avatar_static", acct->@("avatar"), JSON_STRING);
acct->set("header_static", acct->@("header"), JSON_STRING);
acct->set("last_status_at", "0", JSON_STRING);
acct->set("followers_count", 0, JSON_NUMBER);
acct->set("following_count", 0, JSON_NUMBER);
acct->set("statuses_count", 0, JSON_NUMBER);
acct->set("locked", FALSE, JSON_BOOLEAN);
acct->set("bot", FALSE, JSON_BOOLEAN);
acct->set("discoverable", FALSE, JSON_BOOLEAN);
acct->set("indexable", FALSE, JSON_BOOLEAN);
acct->set("hide_collections", FALSE, JSON_BOOLEAN);
acct->set("emojis", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
acct->set("fields", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
source->set("privacy", "public", JSON_STRING);
source->set("sensitive", FALSE, JSON_BOOLEAN);
source->set("language", "", JSON_STRING);
source->set("note", acct->@("note"), JSON_STRING);
source->set("fields", acct->@("fields"), JSON_ARRAY);
source->set("follow_requests_count", 0, JSON_NUMBER);
acct->set("source", source, JSON_OBJECT);
StrPrint(scratch_buffer, "https://%s/@%s", db->o("instance")->@("uri"), acct->@("username"));
acct->set("url", scratch_buffer, JSON_STRING);
db->a("accounts")->append(Json.CreateItem(acct, JSON_OBJECT));
db->o("statuses")->set(acct->@("id"), Json.CreateArray(), JSON_ARRAY);
@slon_admin_create_ap_actor(session, acct);
@slon_db_instance_update_user_count;
@slon_db_save_to_disk;
@slon_free(session, created_at);
@slon_free(session, id);
}
U0 @slon_admin_settings_accounts_new_get(SlonHttpSession* session, U8* buf)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer, request_json;
String.Append(buf, "settings/accounts/new");
String.Append(buf, "<br><br><form action=/settings/accounts/new/save>");
@slon_admin_html_form_from_json_object(session, buf, SLON_DEFAULT_ACCT_OBJECT);
String.Append(buf, "<br><br><input id=save type=submit value=Save></form>");
}
U0 @slon_admin_settings_accounts_get(SlonHttpSession* session, U8* buf)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer, request_json;
String.Append(buf, "settings/accounts");
String.Append(buf, "<br><br><table><tr><th>id</th><th>username</th></tr>");
JsonArray* arr = db->a("accounts");
JsonObject* acct;
I64 i;
for (i = 0; i < arr->length; i++) {
acct = arr->@(i);
if (acct) {
String.Append(buf, "<tr><td>%s</td><td>%s</td></tr>", acct->@("id"), acct->@("username"));
}
}
String.Append(buf, "</table><br><br><button onclick=\"window.location='/settings/accounts/new'\">New</button>");
}
U0 @slon_admin_settings_apps_get(SlonHttpSession* session, U8* buf)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer, request_json;
String.Append(buf, "settings/apps");
String.Append(buf, "<br><br>");
U8* tmp = Json.Stringify(db->o("apps"));
String.Append(buf, tmp);
Free(tmp);
}
U0 @slon_admin_settings_oauth_get(SlonHttpSession* session, U8* buf)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer, request_json;
String.Append(buf, "settings/oauth");
String.Append(buf, "<br><br>");
U8* tmp = Json.Stringify(db->o("oauth"));
String.Append(buf, tmp);
Free(tmp);
}
U0 @slon_admin_settings_instance_save_get(SlonHttpSession* session, U8* buf)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
JsonObject* instance = db->o("instance");
instance->set("uri", request_json->@("uri"));
instance->set("title", request_json->@("title"));
instance->set("short_description", request_json->@("short_description"));
instance->set("description", request_json->@("description"));
instance->set("email", request_json->@("email"));
instance->set("version", request_json->@("version"));
if (!request_json->@("registrations")) {
instance->set("registrations", FALSE);
} else {
instance->set("registrations", !StrICmp("on", request_json->@("registrations")));
}
String.Append(buf, "<script>window.location='/settings/instance';</script>");
}
U0 @slon_admin_settings_instance_get(SlonHttpSession* session, U8* buf)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer, request_json;
String.Append(buf, "settings/instance");
String.Append(buf, "<br><br><form action=/settings/instance/save>");
@slon_admin_html_form_from_json_object(session, buf, db->o("instance"));
String.Append(buf, "<br><br><input type=submit value=Save></form>");
}
U0 @slon_admin_new_account(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
if (db->o("actors")->@(request_json->@("username"))) {
StrPrint(scratch_buffer, "{\"error\":\"account already exists\"}");
@slon_http_set_content_type(session, "application/json");
@slon_http_send(session, scratch_buffer, StrLen(scratch_buffer));
} else {
@slon_admin_create_account(session);
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
}
}
U0 @slon_admin_manage_accounts(SlonHttpSession* session)
{
@slon_http_send_json(session, db->a("accounts"));
}
U0 @slon_admin_info_stats(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
StrPrint(scratch_buffer, "{");
String.Append(scratch_buffer, "\"uptime\":\"%d\"", cnts.jiffies);
String.Append(scratch_buffer, "}");
@slon_http_set_content_type(session, "application/json");
@slon_http_send(session, scratch_buffer, StrLen(scratch_buffer));
}
U0 @slon_admin_server_get(SlonHttpSession* session)
{
if (!db->@("setup")) {
if (StrICmp("/", @slon_http_request_path(session))) {
@slon_http_set_status_code(session, 302);
@slon_http_set_header(session, "Location", "/");
} else {
@slon_http_send_html_file(session, "M:/Slon/Static/html/admin/setup_instance.html");
}
return;
}
if (!StrICmp("/info/stats", @slon_http_request_path(session))) {
@slon_admin_info_stats(session);
return;
}
if (!StrICmp("/manage/accounts", @slon_http_request_path(session))) {
@slon_admin_manage_accounts(session);
return;
}
if (!StrICmp("/", @slon_http_request_path(session))) {
@slon_http_send_html_file(session, "M:/Slon/Static/html/admin/main.html");
return;
}
@slon_http_set_status_code(session, 404);
}
U0 @slon_admin_setup_instance(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
JsonObject* instance = db->o("instance");
instance->set("uri", request_json->@("uri"));
instance->set("title", request_json->@("title"));
instance->set("short_description", request_json->@("description"));
instance->set("description", request_json->@("description"));
instance->set("email", request_json->@("email"));
instance->set("registrations", request_json->@("registrations"));
@slon_db_save_to_disk;
db->set("setup", TRUE);
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
}
U0 @slon_admin_server_post(SlonHttpSession* session)
{
if (StrFind("json", @slon_http_request_header(session, "content-type")) > 0) {
@slon_http_parse_request_as_json(session);
}
if (!StrICmp("/setup/instance", @slon_http_request_path(session))) {
@slon_admin_setup_instance(session);
return;
}
if (!StrICmp("/new/account", @slon_http_request_path(session))) {
@slon_admin_new_account(session);
return;
}
@slon_http_set_status_code(session, 404);
}
U0 @slon_admin_http_handle_get_request(SlonHttpSession* session)
{
if (@slon_http_request_has_query_string(session)) {
@slon_http_parse_query_string(session);
}
@slon_admin_server_get(session);
}
U0 @slon_admin_http_handle_post_request(SlonHttpSession* session)
{
@slon_admin_server_post(session);
}
U0 @slon_admin_http_handle_request(SlonHttpSession* session)
{
switch (@slon_http_request_verb(session)) {
case SLON_HTTP_VERB_GET:
@slon_admin_http_handle_get_request(session);
break;
case SLON_HTTP_VERB_POST:
@slon_admin_http_handle_post_request(session);
break;
default:
@slon_http_set_status_code(session, 405);
}
}
U0 @slon_admin_http_task(TcpSocket* s)
{
// Bail if we can't acquire socket for some reason
if (!@tcp_socket_accept(s))
return;
// Init session
SlonHttpSession* session = @slon_http_init_session(s);
// Parse headers if they are available
while (!@slon_http_request_headers_have_been_parsed(session)) {
@slon_http_receive(session);
// Handle malformed requests (anything less than "GET / HTTP/1.0\r\n\r\n" is probably a bad request)
if (session->request->buffer->size < 18) {
@slon_http_set_status_code(session, 400);
goto slon_admin_http_task_send_response;
}
@slon_http_try_parse_request_headers(session);
}
// If we have a content-length header, consume until we receive all the data, then set request->data pointer and size
if (StrLen(@slon_http_request_header(session, "content-length"))) {
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
while (session->request->buffer->data + session->request->buffer->size - session->request->data < content_length)
@slon_http_receive(session);
}
@slon_admin_http_handle_request(session);
slon_admin_http_task_send_response:
@slon_http_send_response(session);
@slon_http_free_session(session);
s->close();
}
Adam("U0 @spawn_slon_admin_http_task(TcpSocket *s){Spawn(%d, s, \"SlonAdminHttpTask\");};\n", &@slon_admin_http_task);
@tcp_socket_bind(9000, "@spawn_slon_admin_http_task");

232
Slon/Http/LocalServer.HC Normal file
View file

@ -0,0 +1,232 @@
U0 @slon_local_server_set_mime_type(SlonHttpSession* session, U8* filepath)
{
// FIXME: Do this programmatically like the Jakt version, this is awful
if (String.EndsWith(".html", filepath)) {
@slon_http_set_content_type(session, "text/html");
return;
}
if (String.EndsWith(".txt", filepath)) {
@slon_http_set_content_type(session, "text/plain");
return;
}
if (String.EndsWith(".css", filepath)) {
@slon_http_set_content_type(session, "text/css");
return;
}
if (String.EndsWith(".js", filepath)) {
@slon_http_set_content_type(session, "text/javascript");
return;
}
if (String.EndsWith(".json", filepath)) {
@slon_http_set_content_type(session, "application/json");
return;
}
if (String.EndsWith(".gif", filepath)) {
@slon_http_set_content_type(session, "image/gif");
return;
}
if (String.EndsWith(".png", filepath)) {
@slon_http_set_content_type(session, "image/png");
return;
}
if (String.EndsWith(".jpeg", filepath) || String.EndsWith(".jpg", filepath)) {
@slon_http_set_content_type(session, "image/jpeg");
return;
}
@slon_http_set_content_type(session, "application/octet-stream");
}
U0 @slon_local_server_send_file(SlonHttpSession* session, U8* filepath)
{
@slon_local_server_set_mime_type(session, filepath);
@slon_http_send_file(session, filepath);
}
U0 @slon_local_server_directory_listing(SlonHttpSession* session, U8* path)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
U8* html = @slon_calloc(session, 1048576);
String.Append(html, "<html><head><title>Index of ");
String.Append(html, path);
String.Append(html, "</title><style type=text/css>.img-back{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAMAAAD3n0w0AAAAElBMVEX////M//+ZmZlmZmYzMzMAAACei5rnAAAAAnRSTlP/AOW3MEoAAABVSURBVHjabdFBCsBACENR45j7X7kQtC0T//KRjRhYevGgyjBL+VLZUtlS2VItS1AI1QQONgNZHCSUZJAc+ZB3sViFGzPcDmxZqdsvgRB/aJRu73D0HuO2BJfZn2SOAAAAAElFTkSuQmCC)} .img-folder{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAMAAAD3n0w0AAAAElBMVEX/////zJnM//+ZZjMzMzMAAADCEvqoAAAAA3RSTlP//wDXyg1BAAAASElEQVR42s3KAQbAQAxE0W4m//5XboesdihQ6A/ES4566TsyPZE1caNtwmFE22bBuDTtG8ZMaoyZ8Z+fijEWytpYdEZfWGRdJzEsA9OaTRTxAAAAAElFTkSuQmCC)} .img-file{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAWCAMAAAD3n0w0AAAAD1BMVEX////M//+ZmZkzMzMAAABVsTOVAAAAAnRSTlP/AOW3MEoAAAA6SURBVHja3cjHAcAwEIRATqj/lp3jWhUYfoPPag+5EkeII8QRYmB3O3ArENrSB0k8+ivaXrliVY+qZm7SAaxLXnOsAAAAAElFTkSuQmCC)}</style></head><body><h1>Index of ");
String.Append(html, path);
String.Append(html, "</h1><table>");
String.Append(html, "<tr><th></th><th align=left style=padding-right:16px>Name</th><th align=left>Last modified</th><th align=right>Size</th></tr>");
String.Append(html, "<tr><th colspan=4><hr></th></tr>");
StrPrint(scratch_buffer, "A:%s*", path);
CDirEntry* files = FilesFind(scratch_buffer);
CDirEntry* de = files->next;
CDateStruct ds;
while (de) {
String.Append(html, "<tr><td><a href=\"");
String.Append(html, de->name);
String.Append(html, "\">");
if (!StrICmp("..", de->name)) {
String.Append(html, "<img class=img-back alt=Back>");
} else {
if (de->attr & RS_ATTR_DIR) {
String.Append(html, "<img class=img-folder alt=Folder>");
} else {
String.Append(html, "<img class=img-file alt=File>");
}
}
String.Append(html, "</a></td>");
String.Append(html, "<td><a href=\"");
String.Append(html, de->name);
if (de->attr & RS_ATTR_DIR) {
String.Append(html, "/");
}
String.Append(html, "\">");
if (!StrICmp("..", de->name)) {
String.Append(html, "Parent Directory");
} else {
String.Append(html, de->name);
}
String.Append(html, "</a></td><td align=right>");
Date2Struct(&ds, de->datetime);
String.Append(html, "%02d-%03tZ-%04d %02d:%02d", ds.day_of_mon, ds.mon - 1, "ST_MONTHS", ds.year, ds.hour, ds.min);
String.Append(html, " ");
String.Append(html, "</td><td align=right>");
if (de->attr & RS_ATTR_DIR) {
String.Append(html, " - ");
} else {
String.Append(html, "%d", de->size);
}
String.Append(html, "</td></tr>");
de = de->next;
}
DirTreeDel(files);
String.Append(html, "<tr><th colspan=4><hr></th></tr>");
String.Append(html, "</table>");
String.Append(html, "<address>Slon static file webserver for (TempleOS) Server</address>");
String.Append(html, "</body></html>");
@slon_http_set_content_type(session, "text/html");
@slon_http_send(session, html, StrLen(html));
@slon_free(session, html);
}
U0 @slon_local_server_not_found(SlonHttpSession* session)
{
@slon_http_set_status_code(session, 404);
@slon_http_set_content_type(session, "text/html");
@slon_http_send(session, "<h2>404 Not Found</h2>", 22);
}
U0 @slon_local_server_get(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
U8* path = @slon_http_request_path(session);
if (!path || !StrLen(path) || StrFind(":", path) > 0) {
@slon_local_server_not_found(session);
return;
}
if (path[0] == '/' && StrLen(path) == 1) {
// Handle root path
if (FileFind("A:/index.html")) {
@slon_local_server_send_file(session, "A:/index.html");
} else {
@slon_local_server_directory_listing(session, "/");
}
return;
}
if (String.EndsWith("/", path)) {
StrPrint(scratch_buffer, "A:%sindex.html", path);
if (FileFind(scratch_buffer)) {
@slon_local_server_send_file(session, scratch_buffer);
} else {
StrPrint(scratch_buffer, "A:%s", path);
scratch_buffer[StrLen(scratch_buffer) - 1] = NULL;
if (IsDir(scratch_buffer)) {
@slon_local_server_directory_listing(session, path);
} else {
@slon_local_server_not_found(session);
}
}
return;
}
StrPrint(scratch_buffer, "A:%s", path);
if (!FileFind(scratch_buffer)) {
@slon_local_server_not_found(session);
return;
} else {
if (IsDir(scratch_buffer)) {
@slon_http_set_status_code(session, 301);
StrPrint(scratch_buffer, "%s/", path);
@slon_http_set_header(session, "Location", scratch_buffer);
} else {
@slon_local_server_send_file(session, scratch_buffer);
}
return;
}
// shouldn't get here :^)
@slon_http_set_status_code(session, 400);
}
U0 @slon_local_http_handle_get_request(SlonHttpSession* session)
{
@slon_local_server_get(session);
}
U0 @slon_local_http_handle_request(SlonHttpSession* session)
{
switch (@slon_http_request_verb(session)) {
case SLON_HTTP_VERB_GET:
@slon_local_http_handle_get_request(session);
break;
default:
@slon_http_set_status_code(session, 405);
}
}
U0 @slon_local_http_task(TcpSocket* s)
{
// Bail if we can't acquire socket for some reason
if (!@tcp_socket_accept(s))
return;
// Init session
SlonHttpSession* session = @slon_http_init_session(s);
// Parse headers if they are available
while (!@slon_http_request_headers_have_been_parsed(session)) {
@slon_http_receive(session);
// Handle malformed requests (anything less than "GET / HTTP/1.0\r\n\r\n" is probably a bad request)
if (session->request->buffer->size < 18) {
@slon_http_set_status_code(session, 400);
goto slon_local_http_task_send_response;
}
@slon_http_try_parse_request_headers(session);
}
// If we have a content-length header, consume until we receive all the data, then set request->data pointer and size
if (StrLen(@slon_http_request_header(session, "content-length"))) {
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
while (session->request->buffer->data + session->request->buffer->size - session->request->data < content_length)
@slon_http_receive(session);
}
@slon_local_http_handle_request(session);
slon_local_http_task_send_response:
@slon_http_send_response(session);
@slon_http_free_session(session);
s->close();
}
Adam("U0 @spawn_slon_local_http_task(TcpSocket *s){Spawn(%d, s, \"SlonLocalHttpTask\");};\n", &@slon_local_http_task);
@tcp_socket_bind(8000, "@spawn_slon_local_http_task");

542
Slon/Http/Server.HC Normal file
View file

@ -0,0 +1,542 @@
SlonHttpBuffer* @slon_http_init_buffer(SlonHttpSession* session)
{
SlonHttpBuffer* buffer = @slon_calloc(session, sizeof(SlonHttpBuffer));
buffer->data = @slon_calloc(session, SLON_HTTP_BUFFER_SIZE);
buffer->capacity = SLON_HTTP_BUFFER_SIZE;
return buffer;
}
U0 @slon_http_free_response(SlonHttpSession* session, SlonHttpResponse* response)
{
// FIXME: Free headers JsonObject
if (response) {
if (response->buffer && response->buffer->data) {
@slon_free(session, response->buffer->data);
@slon_free(session, response->buffer);
}
@slon_free(session, response);
}
}
U0 @slon_http_free_request(SlonHttpSession* session, SlonHttpRequest* request)
{
// FIXME: Free headers JsonObject
if (request) {
if (request->buffer && request->buffer->data) {
@slon_free(session, request->buffer->data);
@slon_free(session, request->buffer);
}
if (request->verb)
@slon_free(session, session->request->verb);
if (request->raw_path)
@slon_free(session, session->request->raw_path);
if (request->path)
@slon_free(session, session->request->path);
@slon_free(session, request);
}
}
U0 @slon_http_free_session(SlonHttpSession* session)
{
if (!session)
return;
@slon_http_free_response(session, session->response);
@slon_http_free_request(session, session->request);
I64 bytes_used = session->bytes_used - MSize2(session);
Free(session);
if (bytes_used) {
AdamLog("*** Session leaked %d bytes of memory ***\n", bytes_used);
}
}
SlonHttpRequest* @slon_http_init_request(SlonHttpSession* session)
{
SlonHttpRequest* request = @slon_calloc(session, sizeof(SlonHttpRequest));
request->buffer = @slon_http_init_buffer(session);
request->headers = Json.CreateObject();
return request;
}
SlonHttpResponse* @slon_http_init_response(SlonHttpSession* session)
{
SlonHttpResponse* response = @slon_calloc(session, sizeof(SlonHttpResponse));
response->buffer = @slon_http_init_buffer(session);
response->headers = Json.CreateObject();
return response;
}
SlonHttpSession* @slon_http_init_session(TcpSocket* s)
{
SlonHttpSession* session = CAlloc(sizeof(SlonHttpSession), adam_task);
session->bytes_used = MSize2(session);
session->s = s;
session->request = @slon_http_init_request(session);
session->response = @slon_http_init_response(session);
return session;
}
U0 @slon_http_receive(SlonHttpSession* session)
{
// FIXME: grow the buffer
SlonHttpBuffer* buffer = session->request->buffer;
I64 chunk_size = @tcp_socket_receive(session->s, buffer->data + buffer->size, 65536);
buffer->size += chunk_size;
}
Bool @slon_http_request_headers_have_been_parsed(SlonHttpSession* session)
{
return session->request->headers_have_been_parsed;
}
U0 @slon_http_buffer_append(SlonHttpBuffer* buffer, U8* src, I64 size)
{
if (!buffer || !src || !size)
return;
MemCpy(buffer->data + buffer->size, src, size);
buffer->size += size;
}
U0 @slon_http_buffer_append_string(SlonHttpBuffer* buffer, U8* str)
{
@slon_http_buffer_append(buffer, str, StrLen(str));
}
U0 @slon_http_send_response(SlonHttpSession* session)
{
SlonHttpBuffer* buffer = session->response->buffer;
U8 scratch_buffer[256][4];
StrPrint(scratch_buffer[0], "%d", session->response->status_code);
StrPrint(scratch_buffer[1], "HTTP/1.0 %d %s\r\n", session->response->status_code, Json.Get(SLON_HTTP_STATUS_CODES, scratch_buffer[0]));
@slon_http_buffer_append_string(buffer, scratch_buffer[1]);
JsonKey* key = session->response->headers->keys;
while (key) {
StrPrint(scratch_buffer[0], "%s: %s\r\n", key->name, key->value);
@slon_http_buffer_append_string(buffer, scratch_buffer[0]);
key = key->next;
}
StrPrint(scratch_buffer[0], "content-length: %d\r\n", session->response->size);
@slon_http_buffer_append_string(buffer, scratch_buffer[0]);
StrCpy(scratch_buffer[0], "pragma: no-cache\r\n\r\n");
@slon_http_buffer_append_string(buffer, scratch_buffer[0]);
if (session->response->data && session->response->size) {
@slon_http_buffer_append(buffer, session->response->data, session->response->size);
@slon_free(session, session->response->data);
}
@tcp_socket_send(session->s, buffer->data, buffer->size);
}
U0 @slon_http_rstrip_char_from_string(U8* str, I64 ch)
{
while (str[StrLen(str) - 1] == ch)
str[StrLen(str) - 1] = NULL;
}
U0 @slon_http_try_parse_request_headers(SlonHttpSession* session)
{
SlonHttpBuffer* buffer = session->request->buffer;
I64 i = 0;
// Do we have headers yet? let's find out
while (i < buffer->size) {
if (!MemCmp(buffer->data + i, "\r\n\r\n", 4)) {
i += 4;
goto slon_http_parse_request_headers;
}
++i;
}
return;
slon_http_parse_request_headers:
// Set pointer for request content
session->request->data = buffer->data + i;
// We have headers, let's parse them
U8* raw_headers = @slon_calloc(session, i);
MemCpy(raw_headers, buffer->data, i - 4);
I64 raw_header_lines_count = 0;
U8** raw_header_lines = String.Split(raw_headers, '\n', &raw_header_lines_count);
if (!raw_header_lines_count) {
// FIXME: Handle this
}
I64 request_first_line_segments_count = 0;
U8** request_first_line_segments = String.Split(raw_header_lines[0], ' ', &request_first_line_segments_count);
if (request_first_line_segments_count < 2) {
// FIXME: Handle this
}
session->request->verb = @slon_strnew(session, request_first_line_segments[0]);
session->request->raw_path = @slon_strnew(session, request_first_line_segments[1]);
if (StrFind("?", session->request->raw_path)) {
session->request->path = @slon_strnew(session, session->request->raw_path);
*(StrFind("?", session->request->path)) = NULL;
} else {
session->request->path = @slon_strnew(session, session->request->raw_path);
}
U8* key;
U8* value;
for (i = 1; i < raw_header_lines_count; i++) {
key = NULL;
value = NULL;
if (StrFind(": ", raw_header_lines[i])) {
value = StrFind(": ", raw_header_lines[i]) + 2;
@slon_http_rstrip_char_from_string(value, '\r');
*(StrFind(": ", raw_header_lines[i])) = NULL;
key = raw_header_lines[i];
Json.Set(session->request->headers, key, value, JSON_STRING);
}
}
@slon_free(session, raw_headers);
session->request->headers_have_been_parsed = TRUE;
}
U0 @slon_http_authorize(SlonHttpSession* session)
{
if (StrLen(@slon_http_request_header(session, "authorization"))) {
U8* access_token = StrFind(" ", @slon_http_request_header(session, "authorization")) + 1;
session->auth = db->o("oauth")->o("tokens")->@(access_token);
}
}
U0 @slon_http_debug_print_request(SlonHttpSession* session, Bool show_headers = FALSE)
{
AdamLog("[httpd] %d => request: %s %s\n", session->s, session->request->verb, session->request->raw_path);
if (show_headers) {
U8* headers_stringified = Json.Stringify(session->request->headers);
AdamLog("[httpd] %d => headers: %s\n", session->s, headers_stringified);
Free(headers_stringified);
//@slon_free(session, headers_stringified);
}
}
U0 @slon_http_debug_print_response(SlonHttpSession* session, Bool show_headers = FALSE)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
StrPrint(scratch_buffer, "%d", session->response->status_code);
AdamLog("[httpd] %d <= response: %d %s\n", session->s, session->response->status_code, Json.Get(SLON_HTTP_STATUS_CODES, scratch_buffer));
if (show_headers) {
U8* headers_stringified = Json.Stringify(session->response->headers);
AdamLog("[httpd] %d <= headers: %s\n", session->s, headers_stringified);
Free(headers_stringified);
//@slon_free(session, headers_stringified);
}
if (session->response->data) {
AdamLog("data: %s\n", session->response->data);
}
}
U8* @slon_http_json_string_from_form_urlencoded_string(SlonHttpSession* session, U8* form_urlencoded_string)
{
// FIXME: Implement arrays, objects per https://jsonapi.org/format/#fetching
U8* json_string = @slon_calloc(session, StrLen(form_urlencoded_string) * 2);
String.Append(json_string, "{");
U8* form_urlencoded_string_copy = @slon_strnew(session, form_urlencoded_string);
I64 raw_values_count = 0;
U8** raw_values = String.Split(form_urlencoded_string_copy, '&', &raw_values_count);
I64 i = 0;
U8* key;
U8* value;
for (i = 0; i < raw_values_count; i++) {
value = StrFind("=", raw_values[i]) + 1;
*(StrFind("=", raw_values[i])) = NULL;
key = raw_values[i];
U8* decoded_value = @slon_http_decode_urlencoded_string(session, value);
String.Append(json_string, "\"%s\":\"%s\"", key, decoded_value);
@slon_free(session, decoded_value);
if (i < raw_values_count - 1) {
String.Append(json_string, ",");
}
}
String.Append(json_string, "}");
@slon_free(session, form_urlencoded_string_copy);
return json_string;
}
U8* @slon_http_json_string_from_multipart_form_data(SlonHttpSession* session, U8* multipart_form_data)
{
U8* json_string = @slon_calloc(session, StrLen(multipart_form_data) * 2);
String.Append(json_string, "{");
U8* multipart_form_data_copy = @slon_strnew(session, multipart_form_data);
U8* boundary = StrFind("boundary=", @slon_http_request_header(session, "content-type")) + 9;
// Strip begin double-quotes and ending CRLF, double-quotes
while (boundary[0] == '"')
boundary++;
while (boundary[StrLen(boundary) - 1] == '\"' || boundary[StrLen(boundary) - 1] == ' ' || boundary[StrLen(boundary) - 1] == '\r' || boundary[StrLen(boundary) - 1] == '\n')
boundary[StrLen(boundary) - 1] = NULL;
I64 state = SLON_MULTIPART_PARSER_CONSUME_BOUNDARY;
I64 lines_count = 0;
U8** lines = String.Split(multipart_form_data_copy, '\n', &lines_count);
U8* line;
U8* name;
U8* replace_line;
I64 i = 0;
while (i < lines_count) {
line = lines[i];
// Strip any ending CRLF
while (line[StrLen(line) - 1] == '\r' || line[StrLen(line) - 1] == '\n') {
line[StrLen(line) - 1] = NULL;
}
switch (state) {
case SLON_MULTIPART_PARSER_CONSUME_BOUNDARY:
if (StrFind(boundary, line)) {
state = SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION;
}
break;
case SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION:
if (StrFind("ontent-", line) && StrFind("isposition:", line) && StrFind("name=", line)) {
name = StrFind("name=", line) + 5;
// Strip begin/end double-quotes
while (name[0] == '"')
name++;
while (name[StrLen(name) - 1] == '\"')
name[StrLen(name) - 1] = NULL;
String.Append(json_string, "\"%s\":\"", name);
state = SLON_MULTIPART_PARSER_CONSUME_CONTENT;
}
break;
case SLON_MULTIPART_PARSER_CONSUME_CONTENT:
if (StrFind(boundary, line)) {
String.Append(json_string, "\"");
if (!String.EndsWith("--", line)) {
String.Append(json_string, ",");
state = SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION;
} else {
state = SLON_MULTIPART_PARSER_DONE;
}
} else {
replace_line = String.Replace(line, "\"", "\\\"");
String.Append(json_string, replace_line);
Free(replace_line);
}
break;
default:
break;
}
++i;
}
String.Append(json_string, "}");
@slon_free(session, multipart_form_data_copy);
return json_string;
}
U0 @slon_http_parse_query_string(SlonHttpSession* session)
{
U8* raw_path_copy = @slon_strnew(session, session->request->raw_path);
I64 raw_path_split_count = 0;
U8** raw_path_split = String.Split(raw_path_copy, '?', &raw_path_split_count);
if (raw_path_split_count > 1) {
U8* json_string = @slon_http_json_string_from_form_urlencoded_string(session, raw_path_split[1]);
session->request->json = Json.Parse(json_string);
@slon_free(session, json_string);
}
@slon_free(session, raw_path_copy);
}
U0 @slon_http_parse_request_as_form_urlencoded(SlonHttpSession* session)
{
U8* json_string = @slon_http_json_string_from_form_urlencoded_string(session, session->request->data);
session->request->json = Json.Parse(json_string);
@slon_free(session, json_string);
}
U0 @slon_http_parse_request_as_multipart_form_data(SlonHttpSession* session)
{
U8* json_string = @slon_http_json_string_from_multipart_form_data(session, session->request->data);
session->request->json = Json.Parse(json_string);
@slon_free(session, json_string);
}
U0 @slon_http_parse_request_as_json(SlonHttpSession* session)
{
session->request->json = Json.Parse(session->request->data);
}
U0 @slon_http_handle_delete_request(SlonHttpSession* session)
{
/* clang-format off */
#include "Endpoints/Delete/Statuses";
/* clang-format on */
// FIXME: Implement this
@slon_http_send_json(session, SLON_EMPTY_JSON_OBJECT);
}
U0 @slon_http_handle_get_request(SlonHttpSession* session)
{
if (@slon_http_request_has_query_string(session)) {
@slon_http_parse_query_string(session);
}
SLON_DEBUG_PRINT_REQUEST_JSON
/* clang-format off */
#include "Endpoints/Get/Accounts";
#include "Endpoints/Get/ActivityPub";
#include "Endpoints/Get/Blocks";
#include "Endpoints/Get/Bookmarks";
#include "Endpoints/Get/Conversations";
#include "Endpoints/Get/CustomEmojis";
#include "Endpoints/Get/Favourites";
#include "Endpoints/Get/Filters";
#include "Endpoints/Get/FollowRequests";
#include "Endpoints/Get/FollowedTags";
#include "Endpoints/Get/Instance";
#include "Endpoints/Get/Notifications";
#include "Endpoints/Get/OAuth";
#include "Endpoints/Get/Suggestions";
#include "Endpoints/Get/Timelines";
#include "Endpoints/Get/Web";
#include "Endpoints/Get/WellKnown";
/* clang-format on */
@slon_http_set_status_code(session, 404);
}
U0 @slon_http_handle_patch_request(SlonHttpSession* session)
{
if (StrFind("json", @slon_http_request_header(session, "content-type")) > 0) {
@slon_http_parse_request_as_json(session);
}
if (String.BeginsWith("application/x-www-form-urlencoded", @slon_http_request_header(session, "content-type"))) {
@slon_http_parse_request_as_form_urlencoded(session);
}
if (String.BeginsWith("multipart/form-data", @slon_http_request_header(session, "content-type"))) {
@slon_http_parse_request_as_multipart_form_data(session);
}
SLON_DEBUG_PRINT_REQUEST_JSON
/* clang-format off */
#include "Endpoints/Patch/Accounts";
/* clang-format on */
@slon_http_set_status_code(session, 404);
}
U0 @slon_http_handle_post_request(SlonHttpSession* session)
{
if (StrFind("json", @slon_http_request_header(session, "content-type")) > 0) {
@slon_http_parse_request_as_json(session);
}
if (String.BeginsWith("application/x-www-form-urlencoded", @slon_http_request_header(session, "content-type"))) {
@slon_http_parse_request_as_form_urlencoded(session);
}
if (String.BeginsWith("multipart/form-data", @slon_http_request_header(session, "content-type"))) {
@slon_http_parse_request_as_multipart_form_data(session);
}
// Workaround for IceCubesApp: https://github.com/Dimillian/IceCubesApp/issues/2235
if (!StrLen(@slon_http_request_header(session, "content-type")) && @slon_http_request_has_query_string(session)) {
@slon_http_parse_query_string(session);
}
SLON_DEBUG_PRINT_REQUEST_JSON
/* clang-format off */
#include "Endpoints/Post/ActivityPub";
#include "Endpoints/Post/Apps";
#include "Endpoints/Post/OAuth";
#include "Endpoints/Post/Statuses";
/* clang-format on */
@slon_http_set_status_code(session, 404);
}
U0 @slon_http_handle_request(SlonHttpSession* session)
{
// .purge_expired_idempotency_keys()
@slon_http_authorize(session);
switch (@slon_http_request_verb(session)) {
case SLON_HTTP_VERB_DELETE:
@slon_http_handle_delete_request(session);
break;
case SLON_HTTP_VERB_GET:
@slon_http_handle_get_request(session);
break;
case SLON_HTTP_VERB_OPTIONS:
@slon_http_set_status_code(session, 200);
break;
case SLON_HTTP_VERB_PATCH:
@slon_http_handle_patch_request(session);
break;
case SLON_HTTP_VERB_POST:
@slon_http_handle_post_request(session);
break;
default:
@slon_http_set_status_code(session, 405);
}
}
U0 @slon_http_task(TcpSocket* s)
{
// Bail if we can't acquire socket for some reason
if (!@tcp_socket_accept(s))
return;
// Init session
SlonHttpSession* session = @slon_http_init_session(s);
// Parse headers if they are available
while (!@slon_http_request_headers_have_been_parsed(session)) {
@slon_http_receive(session);
// Handle malformed requests (anything less than "GET / HTTP/1.0\r\n\r\n" is probably a bad request)
if (session->request->buffer->size < 18) {
@slon_http_set_status_code(session, 400);
goto slon_http_task_send_response;
}
@slon_http_try_parse_request_headers(session);
}
//@slon_http_debug_print_request(session, FALSE);
// If we have a content-length header, consume until we receive all the data, then set request->data pointer and size
if (StrLen(@slon_http_request_header(session, "content-length"))) {
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
while (session->request->buffer->data + session->request->buffer->size - session->request->data < content_length)
@slon_http_receive(session);
}
@slon_http_handle_request(session);
slon_http_task_send_response:
//@slon_http_debug_print_response(session, FALSE);
@slon_http_send_response(session);
@slon_http_free_session(session);
AdamLog("\n");
s->close();
}
Adam("U0 @spawn_slon_http_task(TcpSocket *s){Spawn(%d, s, \"SlonHttpTask\");};\n", &@slon_http_task);
@tcp_socket_bind(80, "@spawn_slon_http_task");

42
Slon/MakeSlon.HC Normal file
View file

@ -0,0 +1,42 @@
/* clang-format off */
DocMax(Fs);
WinMax(Fs);
#include "Modules/Log";
#include "Modules/Db";
#include "Modules/Http";
#include "Modules/Api";
#include "Api/V1/Accounts";
#include "Api/V1/Apps";
#include "Api/V1/Blocks";
#include "Api/V1/Bookmarks";
#include "Api/V1/Conversations";
#include "Api/V1/CustomEmojis";
#include "Api/V1/Favourites";
#include "Api/V1/Filters";
#include "Api/V1/FollowRequests";
#include "Api/V1/FollowedTags";
#include "Api/V1/Notifications";
#include "Api/V1/Statuses";
#include "Api/V1/Timelines";
#include "Api/V2/Filters";
#include "Api/V2/Instance";
#include "Api/V2/Suggestions";
#include "Modules/ActivityPub";
#include "Modules/Meta";
#include "Modules/OAuth";
#include "Modules/Web";
#include "Modules/Webfinger";
#include "Http/Server";
#include "Http/LocalServer";
#include "Http/AdminServer";
@slon_log(0, "slon is up and running");
@slon_log(0, "instance on port 80, fs on port 8000, admin on port 9000");
WinToTop(adam_task);

795
Slon/Modules/ActivityPub.HC Normal file
View file

@ -0,0 +1,795 @@
U8* @slon_activitypub_strip_double_quotes(U8* str)
{
while (str[0] == '"')
str++;
while (str[StrLen(str) - 1] == '"')
str[StrLen(str) - 1] = NULL;
return str;
}
Bool @slon_activitypub_http_signature_is_valid(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
// 1. Check that we have a signature and digest
if (!StrLen(@slon_http_request_header(session, "signature")) || !StrLen(@slon_http_request_header(session, "digest"))) {
AdamLog("[verify_signature] no signature or digest header present\n");
return FALSE;
}
// 2. Check that digest 1) is SHA-256 and 2) matches content
U8* request_digest = @slon_http_request_header(session, "digest");
if (!(String.BeginsWith("SHA-256", request_digest) || String.BeginsWith("sha-256", request_digest))) {
AdamLog("[verify_signature] digest is not SHA-256\n");
return FALSE;
}
request_digest = StrFind("=", request_digest) + 1;
I64 content_length = Str2I64(@slon_http_request_header(session, "content-length"));
if (!content_length) {
AdamLog("[verify_signature] content-length is 0\n");
return FALSE;
}
U8 content_hash[512];
calc_sha_256(content_hash, session->request->data, content_length);
U8* computed_digest = @base64_encode(content_hash, 32);
if (StrICmp(computed_digest, request_digest)) {
AdamLog("[verify_signature] digest header and computed digest do not match\n");
Free(computed_digest);
return FALSE;
} else {
Free(computed_digest);
}
// Parse values from Signature header
U8* signature_header = @slon_http_request_header(session, "signature");
I64 signature_fragment_count = 0;
U8** signature_fragments = String.Split(signature_header, ',', &signature_fragment_count);
U8* keyId = NULL;
U8* algorithm = NULL;
U8* headers = NULL;
U8* signature = NULL;
I64 i;
for (i = 0; i < signature_fragment_count; i++) {
if (String.BeginsWith("keyId=", signature_fragments[i])) {
keyId = signature_fragments[i] + 6;
keyId = @slon_activitypub_strip_double_quotes(keyId);
}
if (String.BeginsWith("algorithm=", signature_fragments[i])) {
algorithm = signature_fragments[i] + 10;
algorithm = @slon_activitypub_strip_double_quotes(algorithm);
}
if (String.BeginsWith("headers=", signature_fragments[i])) {
headers = signature_fragments[i] + 8;
headers = @slon_activitypub_strip_double_quotes(headers);
}
if (String.BeginsWith("signature=", signature_fragments[i])) {
signature = signature_fragments[i] + 10;
signature = @slon_activitypub_strip_double_quotes(signature);
}
}
// 3. Confirm actor matches keyId
if (!request_json->@("actor")) {
AdamLog("[verify_signature] actor is not present in request\n");
return FALSE;
}
if (!String.BeginsWith(request_json->@("actor"), keyId)) {
AdamLog("[verify_signature] actor does not match keyId\n");
return FALSE;
}
// Check if public key is cached for keyId, if not, fetch it
if (!db->o("public_keys")->@(keyId)) {
@slon_log(LOG_HTTPD, "Actor's public key is not cached, attempting to fetch");
HttpUrl* url = @http_parse_url(request_json->@("actor"));
if (!url) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, malformed url or unspecified error");
return FALSE;
}
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
JsonObject* http_headers = Json.CreateObject();
http_headers->set("accept", "application/json", JSON_STRING);
@http_response* resp = Http.Get(url, fetch_buffer, NULL, http_headers);
if (!resp) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, invalid response from remote server");
Free(fetch_buffer);
return FALSE;
}
while (resp->state != HTTP_STATE_DONE) {
Sleep(1);
}
if (!resp->body.length) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, empty response from remote server");
Free(fetch_buffer);
return FALSE;
}
Free(fetch_buffer);
JsonObject* user_object = Json.Parse(resp->body.data);
if (!user_object) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, user object not present in response from remote server");
return FALSE;
}
JsonObject* pubkey_object = user_object->@("publicKey");
if (!pubkey_object) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, publicKey object not present in user object");
return FALSE;
}
if (!pubkey_object->@("id")) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, id not present in publicKey object");
return FALSE;
}
if (!pubkey_object->@("owner")) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, owner not present in publicKey object");
return FALSE;
}
if (!pubkey_object->@("publicKeyPem")) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, publicKeyPem not present in publicKey object");
return FALSE;
}
if (StrICmp(pubkey_object->@("id"), keyId)) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, keyId does not match id present in publicKey object");
return FALSE;
}
if (StrICmp(pubkey_object->@("owner"), request_json->@("actor"))) {
@slon_log(LOG_HTTPD, "Could not fetch actor's public key, actor does not match owner present in publicKey object");
return FALSE;
}
U8* pem_string = pubkey_object->@("publicKeyPem");
// Convert Base64 PEM to single line
U8* pem_single_line = @slon_calloc(session, StrLen(pem_string));
I64 pem_lines_count = 0;
U8** pem_lines = String.Split(pem_string, '\n', &pem_lines_count);
i = 0;
while (i < pem_lines_count) {
if (pem_lines[i] && StrLen(pem_lines[i]) > 0) {
if (!StrFind("KEY", pem_lines[i])) {
StrCpy(pem_single_line + StrLen(pem_single_line), pem_lines[i]);
}
}
++i;
}
// Decode PEM to DER
I64 der_buf_length = 0;
U8* der_buf = @base64_decode(pem_single_line, &der_buf_length);
// Cache the public key
JsonObject* cached_key = Json.CreateObject();
cached_key->set("key", der_buf, JSON_NUMBER);
cached_key->set("length", der_buf_length, JSON_NUMBER);
db->o("public_keys")->set(keyId, cached_key, JSON_OBJECT);
@slon_free(session, pem_single_line);
Json.Delete(user_object);
Json.Delete(http_headers);
}
// Calculate our signature string allocation
I64 sig_string_alloc_length = 0;
I64 headers_split_count = 0;
U8** headers_split = String.Split(headers, ' ', &headers_split_count);
i = 0;
while (i < headers_split_count) {
sig_string_alloc_length += StrLen(@slon_http_request_header(session, headers_split[i]));
++i;
}
sig_string_alloc_length += StrLen(@slon_http_request_verb(session));
sig_string_alloc_length += StrLen(@slon_http_request_path(session));
sig_string_alloc_length *= 2;
// Construct our signature string
U8* sig_string = @slon_calloc(session, sig_string_alloc_length);
i = 0;
while (i < headers_split_count) {
if (StrLen(headers_split[i]) && headers_split[i][0] >= 'A' && headers_split[i][0] <= 'Z') {
headers_split[i][0] += 'a' - headers_split[i][0];
}
if (!StrCmp("(request-target)", headers_split[i])) {
String.Append(sig_string, "(request-target): %s %s", "post", @slon_http_request_path(session));
} else {
String.Append(sig_string, "%s: %s", headers_split[i], @slon_http_request_header(session, headers_split[i]));
}
++i;
if (i < headers_split_count) {
String.Append(sig_string, "\n");
}
}
// Base64 decode request's signature
I64 verify_sig_buf_length = 0;
U8* verify_sig_buf = @base64_decode(signature, &verify_sig_buf_length);
// Hash our constructed signature string
U8 sig_string_hash[32];
calc_sha_256(sig_string_hash, sig_string, StrLen(sig_string));
// Import RSA key
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
I64 res = @rsa_import(db->o("public_keys")->o(keyId)->@("key"), db->o("public_keys")->o(keyId)->@("length"), rsa_key);
if (res != 0) { // CRYPT_OK = 0
@slon_log(LOG_HTTPD, "Received error from @rsa_import: %d", res);
return FALSE;
}
// Verify signature
I32 stat = 0;
res = @rsa_verify_signature(verify_sig_buf, verify_sig_buf_length, sig_string_hash, 32, &stat, rsa_key);
if (res != 0) { // CRYPT_OK = 0
@slon_log(LOG_HTTPD, "Received error from @rsa_verify_signature: %d", res);
return FALSE;
}
Free(rsa_key);
Free(verify_sig_buf);
@slon_free(session, sig_string);
return stat;
}
U0 @slon_activitypub_users_get(SlonHttpSession* session)
{
U8* path = @slon_strnew(session, @slon_http_request_path(session));
I64 path_segments_count = 0;
U8** path_segments = String.Split(path, '/', &path_segments_count);
if (path_segments_count == 3) {
JsonObject* actor = db->o("actors")->@(path_segments[1]);
if (actor) {
@slon_http_send_ap_json(session, actor);
} else {
@slon_http_set_status_code(session, 404);
}
} else {
@slon_http_set_status_code(session, 400);
}
slon_activitypub_users_get_return:
@slon_free(session, path);
}
U0 @slon_activitypub_async_accept_request(JsonObject* o)
{
JsonObject* request = o->o("request");
if (!StrICmp("accept", request->@("type")) || !StrICmp("reject", request->@("type"))) {
return;
}
Sleep(2000);
U8 scratch_buffer[2048];
U8* this_actor = db->o("actors")->o(o->@("user"))->@("id");
StrPrint(scratch_buffer, "%s/accept/%d", this_actor, Now);
JsonObject* accept_object = Json.CreateObject();
accept_object->set("@context", request->@("@context"), JSON_STRING);
accept_object->set("id", scratch_buffer, JSON_STRING);
accept_object->set("type", "Accept", JSON_STRING);
accept_object->set("actor", this_actor, JSON_STRING);
accept_object->set("object", request, JSON_OBJECT);
U8* accept_object_s = Json.Stringify(accept_object);
U8 content_hash[512];
calc_sha_256(content_hash, accept_object_s, StrLen(accept_object_s));
U8* computed_digest = @base64_encode(content_hash, 32);
JsonObject* http_headers = Json.CreateObject();
StrPrint(scratch_buffer, "%s/inbox", request->@("actor"));
HttpUrl* url = @http_parse_url(scratch_buffer);
CDateStruct ds;
Date2Struct(&ds, Now + 1043910000);
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
ds.year, ds.hour, ds.min, ds.sec);
http_headers->set("Date", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
http_headers->set("Digest", scratch_buffer, JSON_STRING);
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
StrPrint(scratch_buffer, "");
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
String.Append(scratch_buffer, "host: %s\n", url->host);
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
U8* user = StrFind("/users/", this_actor) + 7;
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
if (!private_key_binary) {
I64 private_key_binary_size = 0;
private_key_binary = Json.CreateObject();
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
}
I64 res;
// Import RSA key
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
AdamLog("@rsa_import: res: %d\n", res);
U8 sig[256];
U64 siglen = 256;
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
AdamLog("@rsa_create_signature: res: %d\n", res);
U8* computed_sig = @base64_encode(sig, 256);
StrCpy(scratch_buffer, "");
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
http_headers->set("Signature", scratch_buffer, JSON_STRING);
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
@http_response* resp = Http.Post(url, fetch_buffer, accept_object_s, http_headers);
if (!resp) {
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
Free(fetch_buffer);
return;
}
while (resp->state != HTTP_STATE_DONE) {
Sleep(1);
}
AdamLog("code: %d\n", resp->status.code);
Free(fetch_buffer);
}
U0 @slon_activitypub_follow_request()
{
U8 scratch_buffer[2048];
U8* this_actor = "https://error.checksum.fail/users/alec";
StrPrint(scratch_buffer, "%s/follow/%d", this_actor, Now);
JsonObject* follow_object = Json.CreateObject();
follow_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING);
follow_object->set("id", scratch_buffer, JSON_STRING);
follow_object->set("type", "Follow", JSON_STRING);
follow_object->set("actor", this_actor, JSON_STRING);
follow_object->set("object", "https://techhub.social/users/ryeucrvtexw3", JSON_STRING);
U8* follow_object_s = Json.Stringify(follow_object);
U8 content_hash[512];
calc_sha_256(content_hash, follow_object_s, StrLen(follow_object_s));
U8* computed_digest = @base64_encode(content_hash, 32);
JsonObject* http_headers = Json.CreateObject();
StrPrint(scratch_buffer, "%s/inbox", "https://techhub.social/users/ryeucrvtexw3");
HttpUrl* url = @http_parse_url(scratch_buffer);
CDateStruct ds;
Date2Struct(&ds, Now + 1043910000);
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
ds.year, ds.hour, ds.min, ds.sec);
http_headers->set("Date", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
http_headers->set("Digest", scratch_buffer, JSON_STRING);
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
StrPrint(scratch_buffer, "");
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
String.Append(scratch_buffer, "host: %s\n", url->host);
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
U8* user = StrFind("/users/", this_actor) + 7;
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
if (!private_key_binary) {
I64 private_key_binary_size = 0;
private_key_binary = Json.CreateObject();
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
}
I64 res;
// Import RSA key
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
AdamLog("@rsa_import: res: %d\n", res);
U8 sig[256];
U64 siglen = 256;
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
AdamLog("@rsa_create_signature: res: %d\n", res);
U8* computed_sig = @base64_encode(sig, 256);
StrCpy(scratch_buffer, "");
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
http_headers->set("Signature", scratch_buffer, JSON_STRING);
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
@http_response* resp = Http.Post(url, fetch_buffer, follow_object_s, http_headers);
if (!resp) {
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
Free(fetch_buffer);
return;
}
while (resp->state != HTTP_STATE_DONE) {
Sleep(1);
}
AdamLog("code: %d\n", resp->status.code);
Free(fetch_buffer);
}
U0 @slon_activitypub_async_create_status(JsonObject* status)
{
Sleep(2000);
U8 scratch_buffer[2048];
U8* dest = "https://techhub.social/users/ryeucrvtexw3/inbox";
U8* this_actor = StrNew(status->@("uri"), adam_task);
StrFind("/statuses/", this_actor)[0] = NULL;
JsonObject* create_object = Json.CreateObject();
create_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING);
StrPrint(scratch_buffer, "%s/activity", status->@("uri"));
create_object->set("id", scratch_buffer, JSON_STRING);
create_object->set("type", "Create", JSON_STRING);
create_object->set("actor", this_actor, JSON_STRING);
create_object->set("published", status->@("created_at"), JSON_STRING);
create_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]"), JSON_ARRAY);
JsonArray* cc = Json.CreateArray();
StrPrint(scratch_buffer, "%s/followers", this_actor);
cc->append(Json.CreateItem(scratch_buffer, JSON_STRING));
create_object->set("cc", cc, JSON_ARRAY);
JsonObject* note_object = Json.CreateObject();
note_object->set("id", status->@("uri"), JSON_STRING);
note_object->set("type", "Note", JSON_STRING);
note_object->set("summary", NULL, JSON_NULL);
note_object->set("inReplyTo", NULL, JSON_NULL);
note_object->set("published", status->@("created_at"), JSON_STRING);
note_object->set("attributedTo", this_actor, JSON_STRING);
note_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]"), JSON_ARRAY);
note_object->set("cc", cc, JSON_ARRAY);
note_object->set("sensitive", status->@("sensitive"), JSON_BOOLEAN);
note_object->set("atomUri", status->@("uri"), JSON_STRING);
note_object->set("inReplyToAtomUri", NULL, JSON_NULL);
note_object->set("content", status->@("content"), JSON_STRING);
JsonObject* content_map = Json.CreateObject();
content_map->set("en", status->@("content"), JSON_STRING);
note_object->set("contentMap", content_map, JSON_OBJECT);
note_object->set("attachment", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
note_object->set("tag", SLON_EMPTY_JSON_ARRAY, JSON_ARRAY);
note_object->set("replies", SLON_EMPTY_JSON_OBJECT, JSON_OBJECT);
note_object->set("likes", SLON_EMPTY_JSON_OBJECT, JSON_OBJECT);
note_object->set("shares", SLON_EMPTY_JSON_OBJECT, JSON_OBJECT);
create_object->set("object", note_object, JSON_OBJECT);
U8* create_object_s = Json.Stringify(create_object);
U8 content_hash[512];
calc_sha_256(content_hash, create_object_s, StrLen(create_object_s));
U8* computed_digest = @base64_encode(content_hash, 32);
JsonObject* http_headers = Json.CreateObject();
HttpUrl* url = @http_parse_url(dest);
CDateStruct ds;
Date2Struct(&ds, Now + 1043910000);
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
ds.year, ds.hour, ds.min, ds.sec);
http_headers->set("Date", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
http_headers->set("Digest", scratch_buffer, JSON_STRING);
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
StrPrint(scratch_buffer, "");
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
String.Append(scratch_buffer, "host: %s\n", url->host);
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
U8* user = StrFind("/users/", this_actor) + 7;
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
if (!private_key_binary) {
I64 private_key_binary_size = 0;
private_key_binary = Json.CreateObject();
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
}
I64 res;
// Import RSA key
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
AdamLog("@rsa_import: res: %d\n", res);
U8 sig[256];
U64 siglen = 256;
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
AdamLog("@rsa_create_signature: res: %d\n", res);
U8* computed_sig = @base64_encode(sig, 256);
StrCpy(scratch_buffer, "");
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
http_headers->set("Signature", scratch_buffer, JSON_STRING);
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
@http_response* resp = Http.Post(url, fetch_buffer, create_object_s, http_headers);
if (!resp) {
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
Free(fetch_buffer);
return;
}
while (resp->state != HTTP_STATE_DONE) {
Sleep(1);
}
AdamLog("code: %d\n", resp->status.code);
Free(fetch_buffer);
}
U0 @slon_activitypub_async_delete_status(JsonObject* status)
{
Sleep(2000);
U8 scratch_buffer[2048];
U8* dest = "https://techhub.social/users/ryeucrvtexw3/inbox";
U8* this_actor = StrNew(status->@("uri"), adam_task);
StrFind("/statuses/", this_actor)[0] = NULL;
JsonObject* delete_object = Json.CreateObject();
delete_object->set("@context", "https://www.w3.org/ns/activitystreams", JSON_STRING);
StrPrint(scratch_buffer, "%s#delete", status->@("uri"));
delete_object->set("id", scratch_buffer, JSON_STRING);
delete_object->set("type", "Delete", JSON_STRING);
delete_object->set("actor", this_actor, JSON_STRING);
delete_object->set("to", Json.Parse("[\"https://www.w3.org/ns/activitystreams#Public\"]"), JSON_ARRAY);
JsonObject* ts_object = Json.CreateObject();
ts_object->set("id", status->@("uri"), JSON_STRING);
ts_object->set("type", "Tombstone", JSON_STRING);
ts_object->set("atomUri", status->@("uri"), JSON_STRING);
delete_object->set("object", ts_object, JSON_OBJECT);
U8* delete_object_s = Json.Stringify(delete_object);
U8 content_hash[512];
calc_sha_256(content_hash, delete_object_s, StrLen(delete_object_s));
U8* computed_digest = @base64_encode(content_hash, 32);
JsonObject* http_headers = Json.CreateObject();
HttpUrl* url = @http_parse_url(dest);
CDateStruct ds;
Date2Struct(&ds, Now + 1043910000);
StrPrint(scratch_buffer, "%03tZ, %02d %03tZ %04d %02d:%02d:%02d GMT", ds.day_of_week, "ST_DAYS_OF_WEEK", ds.day_of_mon, ds.mon - 1, "ST_MONTHS",
ds.year, ds.hour, ds.min, ds.sec);
http_headers->set("Date", scratch_buffer, JSON_STRING);
StrPrint(scratch_buffer, "SHA-256=%s", computed_digest);
http_headers->set("Digest", scratch_buffer, JSON_STRING);
http_headers->set("Content-Type", "application/activity+json", JSON_STRING);
StrPrint(scratch_buffer, "");
String.Append(scratch_buffer, "(request-target): post %s\n", url->path);
String.Append(scratch_buffer, "host: %s\n", url->host);
String.Append(scratch_buffer, "date: %s\n", http_headers->@("Date"));
String.Append(scratch_buffer, "digest: %s\n", http_headers->@("Digest"));
String.Append(scratch_buffer, "content-type: %s", http_headers->@("Content-Type"));
AdamLog("headers_to_sign:\n```%s```\n", scratch_buffer);
calc_sha_256(content_hash, scratch_buffer, StrLen(scratch_buffer));
U8* user = StrFind("/users/", this_actor) + 7;
JsonObject* private_key_binary = db->o("private_keys_binary")->o(user);
if (!private_key_binary) {
I64 private_key_binary_size = 0;
private_key_binary = Json.CreateObject();
private_key_binary->set("data", @base64_decode(db->o("private_keys")->@(user), &private_key_binary_size), JSON_OBJECT);
private_key_binary->set("size", private_key_binary_size, JSON_NUMBER);
db->o("private_keys_binary")->set(user, private_key_binary, JSON_OBJECT);
}
I64 res;
// Import RSA key
U64 rsa_key = CAlloc(sizeof(U64) * 32, adam_task);
res = @rsa_import(private_key_binary->@("data"), private_key_binary->@("size"), rsa_key);
AdamLog("@rsa_import: res: %d\n", res);
U8 sig[256];
U64 siglen = 256;
res = @rsa_create_signature(sig, &siglen, content_hash, 32, rsa_key);
AdamLog("@rsa_create_signature: res: %d\n", res);
U8* computed_sig = @base64_encode(sig, 256);
StrCpy(scratch_buffer, "");
String.Append(scratch_buffer, "keyId=\"%s#main-key\",", this_actor);
String.Append(scratch_buffer, "algorithm=\"rsa-sha256\",");
String.Append(scratch_buffer, "headers=\"(request-target) host date digest content-type\",");
String.Append(scratch_buffer, "signature=\"%s\"", computed_sig);
http_headers->set("Signature", scratch_buffer, JSON_STRING);
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
@http_response* resp = Http.Post(url, fetch_buffer, delete_object_s, http_headers);
if (!resp) {
@slon_log(LOG_HTTPD, "Could not POST Accept, invalid response from remote server");
Free(fetch_buffer);
return;
}
while (resp->state != HTTP_STATE_DONE) {
Sleep(1);
}
AdamLog("code: %d\n", resp->status.code);
Free(fetch_buffer);
}
U0 @slon_activitypub_create_status_fedi(JsonObject* status)
{
Spawn(&@slon_activitypub_async_create_status, status, "SlonAsyncCreateTask");
}
U0 @slon_activitypub_delete_status_fedi(JsonObject* status)
{
Spawn(&@slon_activitypub_async_delete_status, status, "SlonAsyncDeleteTask");
}
@slon_api_status_create_fedi = &@slon_activitypub_create_status_fedi;
@slon_api_status_delete_fedi = &@slon_activitypub_delete_status_fedi;
U0 @slon_activitypub_users_inbox(SlonHttpSession* session, U8* user)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
I64 i;
JsonObject* account = @slon_api_account_by_username(user);
Bool already_following = FALSE;
JsonArray* followers = NULL;
JsonArray* statuses = NULL;
JsonObject* status = NULL;
if (!StrICmp("follow", request_json->@("type"))) {
if (!db->o("followers")->@(user)) {
db->o("followers")->set(user, Json.CreateArray(), JSON_ARRAY);
}
followers = db->o("followers")->a(user);
for (i = 0; i < followers->length; i++) {
if (!StrCmp(request_json->@("actor"), followers->@(i))) {
already_following = TRUE;
}
}
if (!already_following) {
followers->append(Json.CreateItem(request_json->@("actor"), JSON_STRING));
account->set("followers_count", account->@("followers_count") + 1);
@slon_db_save_to_disk;
}
}
if (!StrICmp("like", request_json->@("type"))) {
U8* status_id = StrFind("/", StrFind("/statuses/", request_json->@("object")) + 1) + 1;
statuses = db->o("statuses")->a(account->@("id"));
for (i = 0; i < statuses->length; i++) {
status = statuses->@(i);
if (!StrICmp(status_id, status->@("id"))) {
status->set("favourites_count", status->@("favourites_count") + 1);
break;
}
}
@slon_db_save_statuses_to_disk;
}
JsonObject* o = Json.CreateObject();
o->set("user", user, JSON_STRING);
o->set("request", Json.Clone(request_json), JSON_OBJECT);
Spawn(&@slon_activitypub_async_accept_request, o, "SlonAsyncAcceptTask");
@slon_http_set_status_code(session, 200);
return;
}
U0 @slon_activitypub_users_post(SlonHttpSession* session)
{
if (!@slon_activitypub_http_signature_is_valid(session)) {
@slon_http_set_status_code(session, 401);
return;
}
U8* path = @slon_strnew(session, @slon_http_request_path(session));
I64 path_segments_count = 0;
U8** path_segments = String.Split(path, '/', &path_segments_count);
if (path_segments_count < 3) {
@slon_http_set_status_code(session, 400);
goto slon_activitypub_users_post_return;
}
U8* user = path_segments[1];
JsonObject* actor = db->o("actors")->@(user);
if (!actor) {
@slon_http_set_status_code(session, 404);
goto slon_activitypub_users_post_return;
}
U8* method = path_segments[2];
if (!StrICmp("inbox", method)) {
@slon_activitypub_users_inbox(session, user);
goto slon_activitypub_users_post_return;
}
@slon_http_set_status_code(session, 404);
slon_activitypub_users_post_return:
@slon_free(session, path);
}

86
Slon/Modules/Api.HC Normal file
View file

@ -0,0 +1,86 @@
#define SLON_API_LOCAL_TIME_OFFSET 3550
#define SLON_AUTH_ACCOUNT_ID U8* account_id = Json.Get(session->auth, "account_id");
Bool @slon_api_authorized(SlonHttpSession* session)
{
return session->auth > 0;
}
U8* @slon_api_generate_random_hex_string(SlonHttpSession* session, I64 size)
{
U8* str = @slon_calloc(session, (size + 1) * 2);
I64 i;
for (i = 0; i < size; i++) {
String.Append(str, "%02x", RandU64 & 0xff);
}
return str;
}
U8* @slon_api_generate_unique_id(SlonHttpSession* session)
{
U8* unique_id = @slon_calloc(session, 64);
U64 id = ((CDate2Unix(Now) + SLON_API_LOCAL_TIME_OFFSET) * 1000) << 16;
id += RandU64 & 0xffff;
StrPrint(unique_id, "%d", id);
return unique_id;
}
U8* @slon_api_timestamp_from_cdate(SlonHttpSession* session, CDate* date)
{
CDateStruct ds;
Date2Struct(&ds, date);
U8* timestamp = @slon_calloc(session, 32);
StrPrint(timestamp, "%04d-%02d-%02dT%02d:%02d:%02d.000-05:00", ds.year, ds.mon, ds.day_of_mon, ds.hour, ds.min, ds.sec);
return timestamp;
}
Bool @slon_api_boolean_from_string(U8* s)
{
// https://docs.joinmastodon.org/client/intro/#boolean
// True-or-false (Booleans)
// A boolean value is considered false for the values 0, f, F, false, FALSE, off, OFF; considered to not be provided for empty strings;
// and considered to be true for all other values. When using JSON data, use the literals true, false, and null instead.
return !(!StrICmp("0", s) || !StrICmp("f", s) || !StrICmp("false", s) || !StrICmp("off", s));
}
JsonObject* @slon_api_account_by_email(U8* email)
{
if (!email || !StrLen(email))
return NULL;
JsonArray* accts = db->a("accounts");
I64 i;
for (i = 0; i < accts->length; i++) {
if (!StrICmp(accts->o(i)->@("email"), email)) {
return accts->o(i);
}
}
return NULL;
}
JsonObject* @slon_api_account_by_id(U8* id)
{
if (!id || !StrLen(id))
return NULL;
JsonArray* accts = db->a("accounts");
I64 i;
for (i = 0; i < accts->length; i++) {
if (!StrICmp(accts->o(i)->@("id"), id)) {
return accts->o(i);
}
}
return NULL;
}
JsonObject* @slon_api_account_by_username(U8* username)
{
if (!username || !StrLen(username))
return NULL;
JsonArray* accts = db->a("accounts");
I64 i;
for (i = 0; i < accts->length; i++) {
if (!StrICmp(accts->o(i)->@("username"), username)) {
return accts->o(i);
}
}
return NULL;
}

252
Slon/Modules/Db.HC Normal file
View file

@ -0,0 +1,252 @@
#define SLON_DB_PATH "A:/db"
JsonObject* db = Json.CreateObject();
U0 @slon_db_load_accounts_from_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/accounts.json", SLON_DB_PATH);
db->set("accounts", Json.ParseFile(scratch_buffer), JSON_ARRAY);
}
U0 @slon_db_load_actors_from_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/actors.json", SLON_DB_PATH);
db->set("actors", Json.ParseFile(scratch_buffer), JSON_OBJECT);
}
U0 @slon_db_load_apps_from_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/apps.json", SLON_DB_PATH);
db->set("apps", Json.ParseFile(scratch_buffer), JSON_OBJECT);
}
U0 @slon_db_load_instance_from_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/instance.json", SLON_DB_PATH);
db->set("instance", Json.ParseFile(scratch_buffer), JSON_OBJECT);
}
U0 @slon_db_load_oauth_from_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/oauth.json", SLON_DB_PATH);
db->set("oauth", Json.ParseFile(scratch_buffer), JSON_OBJECT);
}
U0 @slon_db_load_private_keys_from_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/private_keys.json", SLON_DB_PATH);
db->set("private_keys", Json.ParseFile(scratch_buffer), JSON_OBJECT);
}
U0 @slon_db_load_followers_from_disk()
{
JsonObject* followers = Json.CreateObject();
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/followers/*.json", SLON_DB_PATH);
CDirEntry* files = FilesFind(scratch_buffer);
CDirEntry* de = files;
JsonArray* follower_array = NULL;
while (de) {
follower_array = Json.ParseFile(de->full_name);
if (follower_array) {
StrFind(".json", de->name)[0] = NULL;
followers->set(de->name, follower_array, JSON_ARRAY);
}
de = de->next;
}
DirTreeDel(files);
db->set("followers", followers, JSON_OBJECT);
}
U0 @slon_db_load_statuses_from_disk()
{
JsonObject* statuses = Json.CreateObject();
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/statuses/*.json", SLON_DB_PATH);
CDirEntry* files = FilesFind(scratch_buffer);
CDirEntry* de = files;
JsonArray* status_array = NULL;
while (de) {
status_array = Json.ParseFile(de->full_name);
if (status_array) {
StrFind(".json", de->name)[0] = NULL;
statuses->set(de->name, status_array, JSON_ARRAY);
}
de = de->next;
}
DirTreeDel(files);
db->set("statuses", statuses, JSON_OBJECT);
}
U0 @slon_db_save_accounts_to_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/accounts.json", SLON_DB_PATH);
Json.DumpToFile(scratch_buffer, db->a("accounts"));
}
U0 @slon_db_save_actors_to_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/actors.json", SLON_DB_PATH);
Json.DumpToFile(scratch_buffer, db->o("actors"));
}
U0 @slon_db_save_apps_to_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/apps.json", SLON_DB_PATH);
Json.DumpToFile(scratch_buffer, db->o("apps"));
}
U0 @slon_db_save_instance_to_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/instance.json", SLON_DB_PATH);
Json.DumpToFile(scratch_buffer, db->o("instance"));
}
U0 @slon_db_save_oauth_to_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/oauth.json", SLON_DB_PATH);
Json.DumpToFile(scratch_buffer, db->o("oauth"));
}
U0 @slon_db_save_private_keys_to_disk()
{
U8 scratch_buffer[256];
StrPrint(scratch_buffer, "%s/private_keys.json", SLON_DB_PATH);
Json.DumpToFile(scratch_buffer, db->o("private_keys"));
}
U0 @slon_db_save_followers_to_disk()
{
U8 scratch_buffer[256];
JsonKey* key = db->o("followers")->keys;
while (key) {
StrPrint(scratch_buffer, "%s/followers/%s.json", SLON_DB_PATH, key->name);
Json.DumpToFile(scratch_buffer, key->value);
key = key->next;
}
}
U0 @slon_db_save_statuses_to_disk()
{
U8 scratch_buffer[256];
JsonKey* key = db->o("statuses")->keys;
while (key) {
StrPrint(scratch_buffer, "%s/statuses/%s.json", SLON_DB_PATH, key->name);
Json.DumpToFile(scratch_buffer, key->value);
key = key->next;
}
}
U0 @slon_db_save_to_disk()
{
@slon_db_save_accounts_to_disk();
@slon_db_save_actors_to_disk();
@slon_db_save_apps_to_disk();
@slon_db_save_followers_to_disk();
@slon_db_save_instance_to_disk();
@slon_db_save_oauth_to_disk();
@slon_db_save_private_keys_to_disk();
@slon_db_save_statuses_to_disk();
}
U0 @slon_db_load_from_defaults()
{
db->set("accounts", Json.CreateArray(), JSON_ARRAY);
db->set("actors", Json.CreateObject(), JSON_OBJECT);
db->set("apps", Json.CreateObject(), JSON_OBJECT);
db->set("idempotency_keys", Json.CreateObject(), JSON_OBJECT);
db->set("private_keys", Json.CreateObject(), JSON_OBJECT);
db->set("private_keys_binary", Json.CreateObject(), JSON_OBJECT);
db->set("public_keys", Json.CreateObject(), JSON_OBJECT);
db->set("followers", Json.CreateObject(), JSON_OBJECT);
db->set("instance", Json.ParseFile("M:/Slon/Static/defaults/instance.json"), JSON_OBJECT);
db->set("statuses", Json.CreateObject(), JSON_OBJECT);
JsonObject* oauth = Json.CreateObject();
oauth->set("codes", Json.CreateObject(), JSON_OBJECT);
oauth->set("requests", Json.CreateObject(), JSON_OBJECT);
oauth->set("responses", Json.CreateObject(), JSON_OBJECT);
oauth->set("tokens", Json.CreateObject(), JSON_OBJECT);
db->set("oauth", oauth, JSON_OBJECT);
db->set("setup", FALSE, JSON_BOOLEAN);
}
U0 @slon_db_load_from_disk()
{
@slon_db_load_accounts_from_disk();
@slon_db_load_actors_from_disk();
@slon_db_load_apps_from_disk();
db->set("idempotency_keys", Json.CreateObject(), JSON_OBJECT);
@slon_db_load_private_keys_from_disk();
db->set("private_keys_binary", Json.CreateObject(), JSON_OBJECT);
db->set("public_keys", Json.CreateObject(), JSON_OBJECT);
@slon_db_load_followers_from_disk();
@slon_db_load_instance_from_disk();
@slon_db_load_oauth_from_disk();
@slon_db_load_statuses_from_disk();
db->set("setup", TRUE, JSON_BOOLEAN);
}
U0 @slon_db_instance_update_user_count()
{
JsonObject* stats = db->o("instance")->o("stats");
stats->set("user_count", db->a("accounts")->length);
}
U0 @slon_db_instance_decrement_status_count()
{
JsonObject* stats = db->o("instance")->o("stats");
stats->set("status_count", MaxI64(0, stats->@("status_count") - 1));
}
U0 @slon_db_instance_increment_status_count()
{
JsonObject* stats = db->o("instance")->o("stats");
stats->set("status_count", stats->@("status_count") + 1);
}
U0 @slon_db_actors_update_user(JsonObject* acct)
{
JsonObject* actors = db->o("actors");
JsonObject* actor = actors->o(acct->@("username"));
if (!actor) {
// FIXME: Handle this error
return;
}
actor->set("name", acct->@("display_name"));
actor->set("summary", acct->@("note"));
JsonObject* icon = actor->o("icon");
icon->set("url", acct->@("avatar"));
actor->set("attachment", acct->@("fields"));
@slon_db_save_actors_to_disk;
}
U0 @slon_db_init()
{
if (FileFind(SLON_DB_PATH)) {
@slon_log(LOG_DB, "loading db from disk");
@slon_db_load_from_disk;
} else {
@slon_log(LOG_DB, "no db found; loading defaults");
@slon_db_load_from_defaults;
}
}
@slon_db_init;
JsonArray* SLON_EMPTY_JSON_ARRAY = Json.CreateArray();
JsonObject* SLON_EMPTY_JSON_OBJECT = Json.CreateObject();
JsonObject* SLON_DEFAULT_ACCT_OBJECT = Json.ParseFile("M:/Slon/Static/defaults/account.json");
JsonObject* SLON_DEFAULT_ACTOR_OBJECT = Json.ParseFile("M:/Slon/Static/defaults/actor.json");

249
Slon/Modules/Http.HC Normal file
View file

@ -0,0 +1,249 @@
#define SLON_HTTP_BUFFER_SIZE 1048576
#define SLON_HTTP_VERB_DELETE 1
#define SLON_HTTP_VERB_GET 2
#define SLON_HTTP_VERB_OPTIONS 3
#define SLON_HTTP_VERB_PATCH 4
#define SLON_HTTP_VERB_POST 5
#define SLON_MULTIPART_PARSER_CONSUME_BOUNDARY 0
#define SLON_MULTIPART_PARSER_CONSUME_CONTENT_DISPOSITION 1
#define SLON_MULTIPART_PARSER_CONSUME_CONTENT 2
#define SLON_MULTIPART_PARSER_DONE 3
#define SLON_SCRATCH_BUFFER_AND_REQUEST_JSON \
U8 scratch_buffer[256]; \
JsonObject* request_json = @slon_http_request_json(session);
#define SLON_DEBUG_PRINT_REQUEST_JSON \
JsonObject* request_json = @slon_http_request_json(session); \
U8* request_json_str = Json.Stringify(request_json); \
AdamLog("request_json: %s\n", request_json_str); \
Free(request_json_str);
JsonObject* SLON_HTTP_STATUS_CODES = Json.ParseFile("M:/Slon/Settings/status_codes.json");
class SlonHttpBuffer {
U8* data;
I64 size;
I64 capacity;
};
class SlonHttpRequest {
SlonHttpBuffer* buffer;
JsonObject* headers;
JsonObject* json;
U8* data;
I64 size;
U8* verb;
U8* raw_path;
U8* path;
Bool headers_have_been_parsed;
};
class SlonHttpResponse {
SlonHttpBuffer* buffer;
JsonObject* headers;
U8* data;
I64 size;
I64 status_code;
};
class SlonHttpSession {
U64 s;
SlonHttpRequest* request;
SlonHttpResponse* response;
I64 bytes_used;
JsonObject* auth;
};
U64 @slon_calloc(SlonHttpSession* session, I64 size)
{
if (!session || !size)
return NULL;
U64 res = CAlloc(size, adam_task);
session->bytes_used += MSize2(res);
// AdamLog("@slon_calloc: requested %d, total used: %d\n", MSize2(res), session->bytes_used);
return res;
}
U0 @slon_free(SlonHttpSession* session, U64 ptr)
{
if (!session || !ptr)
return;
session->bytes_used -= MSize2(ptr);
// AdamLog("@slon_free: freed %d, total used: %d\n", MSize2(ptr), session->bytes_used);
Free(ptr);
}
U64 @slon_malloc(SlonHttpSession* session, I64 size)
{
if (!session || !size)
return NULL;
U64 res = MAlloc(size, adam_task);
session->bytes_used += MSize2(res);
// AdamLog("@slon_malloc: requested %d, total used: %d\n", MSize2(res), session->bytes_used);
return res;
}
U8* @slon_strnew(SlonHttpSession* session, U8* str)
{
if (!session || !str)
return NULL;
U8* new = StrNew(str, adam_task);
session->bytes_used += MSize2(new);
// AdamLog("@slon_strnew: requested %d, total used: %d\n", MSize2(new), session->bytes_used);
// AdamLog("@slon_strnew: %s\n", new);
return new;
}
U8* @slon_http_decode_urlencoded_string(SlonHttpSession* session, U8* str)
{
if (!StrFind("%", str) && !StrFind("+", str)) {
return @slon_strnew(session, str);
}
U8* decoded_string = @slon_calloc(session, StrLen(str));
I64 i = 0;
I64 j;
U32 code_point;
while (i < StrLen(str)) {
if (str[i] == '%') {
code_point = 0;
for (j = 2; j > 0; j--) {
if (str[i + j] >= '0' && str[i + j] <= '9')
code_point += (@t(j == 1, 16, 1) * (str[i + j] - '0'));
if (str[i + j] >= 'A' && str[i + j] <= 'F')
code_point += (@t(j == 1, 16, 1) * (10 + (str[i + j] - 'A')));
if (str[i + j] >= 'a' && str[i + j] <= 'f')
code_point += (@t(j == 1, 16, 1) * (10 + (str[i + j] - 'a')));
}
String.Append(decoded_string, "%c", code_point);
i += 3;
} else if (str[i] == '+') {
String.Append(decoded_string, " ");
i++;
} else {
String.Append(decoded_string, "%c", str[i]);
i++;
}
}
return decoded_string;
}
JsonObject* @slon_http_request_json(SlonHttpSession* session)
{
if (!session->request->json)
return SLON_EMPTY_JSON_OBJECT;
return session->request->json;
}
U0 @slon_http_set_header(SlonHttpSession* session, U8* key, U8* value)
{
Json.Set(session->response->headers, key, value, JSON_STRING);
}
U0 @slon_http_set_content_type(SlonHttpSession* session, U8* value)
{
@slon_http_set_header(session, "content-type", value);
}
U0 @slon_http_set_status_code(SlonHttpSession* session, I64 status_code)
{
session->response->status_code = status_code;
}
U0 @slon_http_send_ap_json(SlonHttpSession* session, U64 json)
{
// a stringified copy of "json" is created, a strnew is sent, we clean up stringified copy, sender cleans up "json"
@slon_http_set_status_code(session, 200);
@slon_http_set_content_type(session, "application/activity+json; charset=utf-8");
U8* json_string = Json.Stringify(json);
session->response->data = @slon_strnew(session, json_string);
session->response->size = StrLen(session->response->data);
Free(json_string);
}
U0 @slon_http_send_json(SlonHttpSession* session, U64 json)
{
// a stringified copy of "json" is created, a strnew is sent, we clean up stringified copy, sender cleans up "json"
@slon_http_set_status_code(session, 200);
@slon_http_set_content_type(session, "application/json; charset=utf-8");
U8* json_string = Json.Stringify(json);
session->response->data = @slon_strnew(session, json_string);
session->response->size = StrLen(session->response->data);
Free(json_string);
}
U0 @slon_http_send_string(SlonHttpSession* session, U8* str)
{
// a strnew of "str" is sent, sender cleans up "str"
@slon_http_set_status_code(session, 200);
session->response->data = @slon_strnew(session, str);
session->response->size = StrLen(str);
}
U0 @slon_http_send(SlonHttpSession* session, U64 data, I64 size)
{
// a malloc copy of "data" is sent, sender cleans up "data"
@slon_http_set_status_code(session, 200);
U8* data_new = @slon_malloc(session, size);
MemCpy(data_new, data, size);
session->response->data = data_new;
session->response->size = size;
}
U0 @slon_http_send_file(SlonHttpSession* session, U8* path)
{
if (!session || !path)
return;
if (!FileFind(path))
return;
I64 size = 0;
U8* data = FileRead(path, &size);
@slon_http_send(session, data, size);
Free(data);
}
U0 @slon_http_send_html_file(SlonHttpSession* session, U8* path)
{
@slon_http_set_content_type(session, "text/html");
@slon_http_send_file(session, path);
}
U0 @slon_http_send_json_file(SlonHttpSession* session, U8* path, U8* content_type = "application/json; charset=utf-8")
{
@slon_http_set_content_type(session, content_type);
@slon_http_send_file(session, path);
}
U8* @slon_http_request_path(SlonHttpSession* session)
{
return session->request->path;
}
I64 @slon_http_request_verb(SlonHttpSession* session)
{
if (!StrCmp(session->request->verb, "DELETE"))
return SLON_HTTP_VERB_DELETE;
if (!StrCmp(session->request->verb, "GET"))
return SLON_HTTP_VERB_GET;
if (!StrCmp(session->request->verb, "OPTIONS"))
return SLON_HTTP_VERB_OPTIONS;
if (!StrCmp(session->request->verb, "PATCH"))
return SLON_HTTP_VERB_PATCH;
if (!StrCmp(session->request->verb, "POST"))
return SLON_HTTP_VERB_POST;
return 999;
}
U8* @slon_http_request_header(SlonHttpSession* session, U8* key)
{
U64 value = Json.Get(session->request->headers, key);
if (!value)
return "";
return value;
}
Bool @slon_http_request_has_query_string(SlonHttpSession* session)
{
return StrFind("?", session->request->raw_path) > 0 && !String.EndsWith("?", session->request->raw_path);
}

22
Slon/Modules/Log.HC Normal file
View file

@ -0,0 +1,22 @@
#define LOG_DB 100
#define LOG_HTTPD 101
U0 @slon_log(I64 module, U8* fmt, ...)
{
CDateStruct ds;
Date2Struct(&ds, Now);
AdamLog("[%02d:%02d]", ds.hour, ds.min);
switch (module) {
case LOG_DB:
AdamLog("[ slon/db] ");
break;
case LOG_HTTPD:
AdamLog("[slon/httpd] ");
break;
default:
AdamLog("[ slon/misc] ");
break;
}
AdamLog(fmt, argc, argv);
AdamLog("\n");
}

9
Slon/Modules/Meta.HC Normal file
View file

@ -0,0 +1,9 @@
U0 @slon_host_meta(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
StrPrint(scratch_buffer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><XRD xmlns=\"http://docs.oasis-open.org/ns/xri/xrd-1.0\"><Link rel=\"lrdd\" template=\"https://%s/.well-known/webfinger?resource={uri}\"/></XRD>", db->o("instance")->@("uri"));
@slon_http_set_content_type(session, "application/xrd+xml; charset=utf-8");
@slon_http_send_string(session, scratch_buffer);
}

192
Slon/Modules/OAuth.HC Normal file
View file

@ -0,0 +1,192 @@
#define SLON_OAUTH_USERINFO_URL "https://app.simplelogin.io/oauth2/userinfo?access_token="
U0 @slon_oauth_well_known(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn request_json;
StrPrint(scratch_buffer, "{\"issuer\":\"https://%s\",\"authorization_endpoint\":\"https://%s/oauth/authorize\",\"response_types_supported\":[\"code\"],\"app_registration_endpoint\":\"https://%s/api/v1/apps\"}",
db->o("instance")->@("uri"), db->o("instance")->@("uri"), db->o("instance")->@("uri"));
@slon_http_set_content_type(session, "application/json; charset=utf-8");
@slon_http_send_string(session, scratch_buffer);
}
U0 @slon_oauth_fetch_token(U8* client_id)
{
if (!client_id || !StrLen(client_id))
return;
U8 url_string[256];
JsonObject* oauth_request = db->o("oauth")->o("requests")->@(client_id);
if (!oauth_request)
return;
U8* access_token = oauth_request->@("access_token");
if (!access_token) {
return;
}
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
StrPrint(url_string, "%s%s", SLON_OAUTH_USERINFO_URL, access_token);
@http_response* resp = fetch(url_string, fetch_buffer);
if (!resp)
goto oauth_free_and_return;
if (resp->body.length) {
// POSIX people think JSON should end with a new line, and the Jakt parser disagrees :^)
while (resp->body.data[StrLen(resp->body.data) - 1] == '\n')
resp->body.data[StrLen(resp->body.data) - 1] = NULL;
JsonObject* response = Json.Parse(resp->body.data);
db->o("oauth")->o("responses")->set(client_id, response, JSON_OBJECT);
}
// FIXME: Free resp
oauth_free_and_return:
Free(fetch_buffer);
Free(client_id);
}
U0 @async_slon_oauth_fetch_token(U8* client_id)
{
Spawn(&@slon_oauth_fetch_token, StrNew(client_id, adam_task), "OauthFetchTokenTask");
}
U8* @slon_oauth_generate_access_token(SlonHttpSession* session)
{
return @slon_api_generate_random_hex_string(session, 16);
}
U8* @slon_oauth_generate_authorization_code(SlonHttpSession* session)
{
return @slon_api_generate_random_hex_string(session, 16);
}
U0 @slon_oauth_verify_access_get(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
U8* client_id = request_json->@("client_id");
U8* redirect_uri = request_json->@("redirect_uri");
JsonObject* app_object = db->o("apps")->@(client_id);
// If client_id or redirect_uri are empty, or if client app doesn't exist, Bad Request
if (!StrLen(client_id) || !StrLen(redirect_uri) || !app_object) {
@slon_http_set_status_code(session, 400);
return;
}
U8* client_secret = app_object->@("client_secret");
JsonObject* userinfo = db->o("oauth")->o("responses")->@(client_id);
if (userinfo) {
// If a userinfo with the client_id exists, read the userinfo Object.
U8* email = userinfo->@("email");
if (email && StrLen(email)) {
JsonObject* acct = @slon_api_account_by_email(email);
if (acct) {
// If the account exists,
// create a token that points to the account
U8* access_token = NULL;
Bool access_token_exists = TRUE;
while (access_token_exists) {
if (access_token) {
@slon_free(session, access_token);
}
access_token = @slon_oauth_generate_access_token(session);
access_token_exists = db->o("oauth")->o("tokens")->@(access_token) > 0;
}
I64 created_at = ToF64(CDate2Unix(Now));
JsonObject* token_object = Json.CreateObject();
token_object->set("access_token", access_token, JSON_STRING);
token_object->set("token_type", "Bearer", JSON_STRING);
token_object->set("scope", "read write follow push", JSON_STRING);
token_object->set("created_at", created_at, JSON_NUMBER);
token_object->set("account_id", acct->@("id"), JSON_STRING);
token_object->set("client_id", client_id, JSON_STRING);
token_object->set("email", email, JSON_STRING);
db->o("oauth")->o("tokens")->set(access_token, token_object, JSON_OBJECT);
// FIXME: We need to commit this to disk eventually? but not immediately
U8* authorization_code = NULL;
Bool authorization_code_exists = TRUE;
while (authorization_code_exists) {
if (authorization_code) {
@slon_free(session, authorization_code);
}
authorization_code = @slon_oauth_generate_authorization_code(session);
authorization_code_exists = db->o("oauth")->o("codes")->@(authorization_code) > 0;
}
JsonObject* code_object = Json.CreateObject();
code_object->set("access_token", access_token, JSON_STRING);
code_object->set("token_type", "Bearer", JSON_STRING);
code_object->set("scope", "read write follow push", JSON_STRING);
code_object->set("created_at", created_at, JSON_NUMBER);
code_object->set("account_id", acct->@("id"), JSON_STRING);
code_object->set("client_id", client_id, JSON_STRING);
code_object->set("client_secret", client_secret, JSON_STRING);
code_object->set("email", email, JSON_STRING);
db->o("oauth")->o("codes")->set(authorization_code, code_object, JSON_OBJECT);
@slon_db_save_oauth_to_disk;
StrPrint(scratch_buffer, "%s?code=%s", redirect_uri, authorization_code);
JsonObject* redirect_uri_object = Json.CreateObject();
redirect_uri_object->set("redirect_uri", scratch_buffer, JSON_STRING);
@slon_http_send_json(session, redirect_uri_object);
Json.Delete(redirect_uri_object);
@slon_free(session, authorization_code);
@slon_free(session, access_token);
} else {
// If the account does not exist, return Not Found
@slon_http_set_status_code(session, 404);
}
} else {
// Response doesn't contain an email, therefore user is Unauthorized.
@slon_http_set_status_code(session, 401);
}
return;
} else {
if (!db->o("oauth")->o("requests")->@(client_id)) {
// If a request with the client_id does not exist, create one, and spawn a fetch() instance to retrieve the OAuth2 token.
db->o("oauth")->o("requests")->set(client_id, request_json, JSON_OBJECT);
@async_slon_oauth_fetch_token(client_id);
}
@slon_http_set_status_code(session, 202);
}
Json.Delete(app_object);
}
U0 @slon_oauth_token_post(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer;
U8* client_id = request_json->@("client_id");
U8* client_secret = request_json->@("client_secret");
U8* code = request_json->@("code");
JsonObject* code_object = db->o("oauth")->o("codes")->@(code);
if (!StrLen(client_id) || !StrLen(client_secret) || !code_object) {
// If client_id is empty, or client_secret is empty, or the code doesn't exist, it's a Bad Request.
@slon_http_set_status_code(session, 400);
return;
}
U8* access_token = code_object->@("access_token");
if (!StrCmp(client_id, code_object->@("client_id")) && !StrCmp(client_secret, code_object->@("client_secret"))) {
JsonObject* token = db->o("oauth")->o("tokens")->@(access_token);
if (token) {
@slon_http_send_json(session, token);
} else {
// If the token doesn't exist, Page Expired?
@slon_http_set_status_code(session, 419);
}
} else {
// If client_id and client_secret do not match, it's Unauthorized
@slon_http_set_status_code(session, 401);
}
Json.Delete(code_object);
}

42
Slon/Modules/Web.HC Normal file
View file

@ -0,0 +1,42 @@
U0 @slon_web_user_get(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
no_warn scratch_buffer, request_json;
I64 path_segments_count = 0;
U8** path_segments = String.Split(StrFind("@", @slon_http_request_path(session)) + 1, '/', &path_segments_count);
U8* user = path_segments[0];
if (path_segments_count == 1) {
JsonObject* actor = db->o("actors")->@(user);
if (!actor) {
@slon_http_set_status_code(session, 404);
goto slon_web_user_get_return;
}
// gib profil pl0x
I64 html_file_size;
U8* html_file_data = FileRead("M:/Slon/Static/html/user.html", &html_file_size);
U8* user_file_data = Json.Stringify(actor);
U8* html_data = @slon_calloc(session, (html_file_size * 2) + (StrLen(user_file_data) * 2));
String.Append(html_data, html_file_data);
String.Append(html_data, "<script>getStatuses(");
String.Append(html_data, user_file_data);
String.Append(html_data, ");</script>");
@slon_http_set_content_type(session, "text/html");
@slon_http_send(session, html_data, StrLen(html_data));
Free(html_file_data);
Free(user_file_data);
@slon_free(session, html_data);
goto slon_web_user_get_return;
} else {
// do something here (statuses, followers, media, etc.)
@slon_http_set_status_code(session, 404);
}
slon_web_user_get_return:
Free(path_segments);
}

39
Slon/Modules/Webfinger.HC Normal file
View file

@ -0,0 +1,39 @@
U0 @slon_webfinger(SlonHttpSession* session)
{
SLON_SCRATCH_BUFFER_AND_REQUEST_JSON
U8* resource = request_json->@("resource");
StrPrint(scratch_buffer, "@%s", db->o("instance")->@("uri"));
if (resource && String.BeginsWith("acct:", resource) && String.EndsWith(scratch_buffer, resource)) {
resource = StrFind(":", resource) + 1;
StrFind("@", resource)[0] = NULL;
if (db->o("actors")->@(resource)) {
JsonObject* webfinger_object = Json.CreateObject();
StrPrint(scratch_buffer, "acct:%s@%s", resource, db->o("instance")->@("uri"));
webfinger_object->set("subject", scratch_buffer, JSON_STRING);
JsonArray* aliases = Json.CreateArray();
StrPrint(scratch_buffer, "https://%s/@%s", db->o("instance")->@("uri"), resource);
aliases->append(Json.CreateItem(scratch_buffer, JSON_STRING));
StrPrint(scratch_buffer, "https://%s/users/%s", db->o("instance")->@("uri"), resource);
aliases->append(Json.CreateItem(scratch_buffer, JSON_STRING));
webfinger_object->set("aliases", aliases, JSON_ARRAY);
JsonArray* links = Json.CreateArray();
JsonObject* link_object = Json.CreateObject();
link_object->set("rel", "self", JSON_STRING);
link_object->set("type", "application/activity+json", JSON_STRING);
StrPrint(scratch_buffer, "https://%s/users/%s", db->o("instance")->@("uri"), resource);
link_object->set("href", scratch_buffer, JSON_STRING);
links->append(Json.CreateItem(link_object, JSON_OBJECT));
webfinger_object->set("links", links, JSON_ARRAY);
@slon_http_send_json(session, webfinger_object);
Json.Delete(webfinger_object);
} else {
@slon_http_set_status_code(session, 404);
}
} else {
@slon_http_set_status_code(session, 400);
}
}

View file

@ -0,0 +1 @@
{"100": "Continue", "101": "Switching Protocols", "102": "Processing", "103": "Early Hints", "200": "OK", "201": "Created", "202": "Accepted", "203": "Non-Authoritative Information", "204": "No Content", "205": "Reset Content", "206": "Partial Content", "207": "Multi-Status", "208": "Already Reported", "218": "This is fine", "226": "IM Used", "300": "Multiple Choices", "301": "Moved Permanently", "302": "Found", "303": "See Other", "304": "Not Modified", "306": "Switch Proxy", "307": "Temporary Redirect", "308": "Resume Incomplete", "400": "Bad Request", "401": "Unauthorized", "402": "Payment Required", "403": "Forbidden", "404": "Not Found", "405": "Method Not Allowed", "406": "Not Acceptable", "407": "Proxy Authentication Required", "408": "Request Timeout", "409": "Conflict", "410": "Gone", "411": "Length Required", "412": "Precondition Failed", "413": "Request Entity Too Large", "414": "Request-URI Too Long", "415": "Unsupported Media Type", "416": "Requested Range Not Satisfiable", "417": "Expectation Failed", "418": "I'm a teapot", "419": "Page Expired", "420": "Method Failure", "421": "Misdirected Request", "422": "Unprocessable Entity", "423": "Locked", "424": "Failed Dependency", "426": "Upgrade Required", "428": "Precondition Required", "429": "Too Many Requests", "431": "Request Header Fields Too Large", "440": "Login Time-out", "444": "Connection Closed Without Response", "449": "Retry With", "450": "Blocked by Windows Parental Controls", "451": "Unavailable For Legal Reasons", "494": "Request Header Too Large", "495": "SSL Certificate Error", "496": "SSL Certificate Required", "497": "HTTP Request Sent to HTTPS Port", "498": "Invalid Token", "499": "Client Closed Request", "500": "Internal Server Error", "501": "Not Implemented", "502": "Bad Gateway", "503": "Service Unavailable", "504": "Gateway Timeout", "505": "HTTP Version Not Supported", "506": "Variant Also Negotiates", "507": "Insufficient Storage", "508": "Loop Detected", "509": "Bandwidth Limit Exceeded", "510": "Not Extended", "511": "Network Authentication Required", "520": "Unknown Error", "521": "Web Server Is Down", "522": "Connection Timed Out", "523": "Origin Is Unreachable", "524": "A Timeout Occurred", "525": "SSL Handshake Failed", "526": "Invalid SSL Certificate", "527": "Railgun Listener to Origin Error", "530": "Origin DNS Error", "598": "Network Read Timeout Error"}

117
Slon/Static/css/main.css Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,8 @@
{
"username": "",
"display_name": "",
"email": "",
"note": "",
"avatar": "",
"header": ""
}

View file

@ -0,0 +1,44 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
{
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"alsoKnownAs": {
"@id": "as:alsoKnownAs",
"@type": "@id"
},
"movedTo": {
"@id": "as:movedTo",
"@type": "@id"
},
"schema": "http://schema.org#",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"discoverable": "toot:discoverable",
"suspended": "toot:suspended",
"memorial": "toot:memorial",
"indexable": "toot:indexable",
"attributionDomains": {
"@id": "toot:attributionDomains",
"@type": "@id"
}
}
],
"type": "Person",
"manuallyApprovesFollowers": false,
"discoverable": false,
"indexable": false,
"memorial": false,
"tag": [],
"endpoints": {}
}

View file

@ -0,0 +1,14 @@
{
"uri": "error.checksum.fail",
"title": "error.checksum.fail",
"short_description": "A fediverse instance running on TempleOS",
"description": "A fediverse instance running on TempleOS",
"email": "alec@checksum.fail",
"version": "1.0.0",
"registrations": false,
"stats": {
"user_count": 0,
"status_count": 0,
"domain_count": 0
}
}

View file

@ -0,0 +1,168 @@
<!doctypehtml>
<link href=https://cdn.jsdelivr.net/npm/bulma@1.0.3/css/bulma.min.css rel=stylesheet>
<style>
body {
padding: 32px
}
.container-x {
width: 640px
}
.next {
text-align: right
}
.spacer {
height: 16px
}
.main-content {
padding-left: 32px;
width: 100%;
vertical-align: top
}
.menu {
width: 240px
}
</style>
<aside class="menu is-inline-block">
<p class="menu-label">Info</p>
<ul class="menu-list">
<li><a onclick="infoStats()" id="menuitem-stats">Statistics</a></li>
</ul>
<p class="menu-label">Manage</p>
<ul class="menu-list">
<li><a onclick="manageAccounts()" id="menuitem-accounts">Accounts</a></li>
<li><a onclick="manageInstance()" id="menuitem-instance">Instance</a></li>
</ul>
<p class="menu-label">Diagnostics</p>
<ul class="menu-list">
<li><a onclick="diagsLogs()" id="menuitem-logs">Logs</a></li>
</ul>
</aside>
<div id="content" class="container main-content is-inline-block">
</div>
<script>
function clearActiveLinks() {
document.querySelectorAll("a").forEach(function (a) { a.className = ""; });
}
function setActiveLink(link) {
document.getElementById("menuitem-" + link).className = "is-active";
}
function setContent(html) {
document.getElementById("content").innerHTML = html;
}
async function infoStats() {
clearActiveLinks();
const request = new Request("/info/stats");
const response = await fetch(request);
const stats = await response.json();
let html = "<h4 class=\"title is-4\">Statistics</h4><div class=spacer></div><div>Uptime: " + formatTime(stats["uptime"]) + "</div>";
setContent(html);
setActiveLink("stats");
}
async function manageAccounts() {
clearActiveLinks();
const request = new Request("/manage/accounts");
const response = await fetch(request);
const accounts = await response.json();
let html = "<h4 class=\"title is-4\">Accounts</h4><div class=spacer></div>";
if (accounts.length) {
html += "<table class=table><thead><tr><th>id</th><th>username</th></tr></head><tbody>";
for (let i = 0; i < accounts.length; i++) {
html += "<tr><td>" + accounts[i]["id"] + "</td><td>" + accounts[i]["username"] + "</td><tr>";
}
html += "</tbody></table>";
} else {
html += "No users";
}
html += "<br><br><input onclick=manageNewUser() class=button type=button value=\"New User\">";
setContent(html);
setActiveLink("accounts");
}
function manageNewUser() {
clearActiveLinks();
let html = "<h4 class=\"title is-4\">New User</h4><div class=spacer></div>";
html += "<form action=\"javascript:saveNewUser()\"><div>";
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
html += "<label class=label>Username</label><div class=control><input id=username class=input placeholder=baoh required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Display Name</label><div class=control><input id=display_name class=input placeholder=\"Ikuro Hashizawa\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Email</label><div class=control><input id=email class=input type=email placeholder=\"cooldude42069@checksum.fail\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Bio</label><div class=control><input id=bio class=input placeholder=\"ima firin mah lazer cannon\" required autocomplete=off></div><div class=spacer></div>";
html += "</div>";
html += "<div class=\"section is-inline-block\" style=\"width:420px;vertical-align:top\">";
html += "<label class=label>Avatar</label><div class=control><input id=avatar class=input placeholder=\"https://full.path.to/my/avatar.png\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Header</label><div class=control><input id=header class=input placeholder=\"https://full.path.to/my/header.png\" required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Private Key (must be in DER format)</label><div class=control><input onchange=updateBase64(this) id=privatekey type=file required autocomplete=off></div><div class=spacer></div>";
html += "<label class=label>Public Key (must be in PEM format)</label><div class=control><input onchange=updateBase64(this) id=publickey type=file required autocomplete=off></div><div class=spacer></div>";
html += "</div>";
html += "</div>";
html += "<div class=\"control next\"><input class=\"button is-link\" type=submit value=Save></div>"
html += "</div></form>";
setContent(html);
setActiveLink("accounts");
}
async function saveNewUser() {
let data = {};
let fields = document.getElementsByTagName("input");
for (var i = 0; i < fields.length; i++) {
switch (fields[i].type) {
case "checkbox":
data[fields[i].id] = fields[i].checked;
break;
case "file":
data[fields[i].id] = fields[i].base64;
break;
case "submit":
break;
default:
data[fields[i].id] = fields[i].value;
break;
}
}
const request = new Request("/new/account", {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify(data)
});
const response = await fetch(request);
const json = await response.json();
if (!Object.keys(json).length) {
manageAccounts();
} else {
alert(JSON.stringify(json));
}
}
function updateBase64(el) {
let reader = new FileReader();
reader.readAsDataURL(el.files[0]);
reader.addEventListener(
"load",
() => {
el.base64 = reader.result.split(";base64,")[1];
}
);
}
const formatTime = milliseconds => {
const seconds = Math.floor((milliseconds / 1000) % 60);
const minutes = Math.floor((milliseconds / 1000 / 60) % 60);
const hours = Math.floor((milliseconds / 1000 / 60 / 60) % 24);
return [
hours.toString().padStart(2, "0"),
minutes.toString().padStart(2, "0"),
seconds.toString().padStart(2, "0")
].join(":");
}
addEventListener("DOMContentLoaded", (event) => {
infoStats();
})
document.querySelectorAll("input[type=file]").forEach(function (e) {
a.className = "";
});
</script>

View file

@ -0,0 +1,83 @@
<!doctypehtml>
<link href=https://cdn.jsdelivr.net/npm/bulma@1.0.3/css/bulma.min.css rel=stylesheet>
<style>
body {
padding: 32px
}
.container {
width: 640px
}
.next {
text-align: right
}
.spacer {
height: 16px
}
</style>
<div class=container>
<nav class=panel>
<form action="javascript:setupInstance()">
<p class=panel-heading>Setup
<div class=section>
<p>Enter the following information to set up your Slon instance.
<div class=spacer></div>
<label class=label>URI</label>
<div class=control><input id=uri class=input placeholder=my-slon-instance.foo required
autocomplete=off></div>
<div style=height:16px></div>
<label class=label>Title</label>
<div class=control><input id=title class=input placeholder="My Slon Instance" required
autocomplete=off></div>
<div class=spacer></div>
<label class=label>Description</label>
<div class=control><input id=description class=input
placeholder="A fediverse instance running on TempleOS" required autocomplete=off></div>
<div class=spacer></div>
<label class=label>Administrator Email</label>
<div class=control><input id=email class=input placeholder=alec@checksum.fail required type=email
autocomplete=off>
</div>
<div class=spacer></div>
<label class=checkbox><input id=registrations type=checkbox> Enable
registrations</label>
<div class="control next"><input class="button is-link" type=submit value=Next></div>
</div>
</form>
</nav>
</div>
<script>
addEventListener("DOMContentLoaded", (event) => {
document.getElementById("uri").focus();
})
async function setupInstance() {
let data = {};
let fields = document.getElementsByTagName("input");
for (var i = 0; i < fields.length; i++) {
switch (fields[i].type) {
case "checkbox":
data[fields[i].id] = fields[i].checked;
break;
case "submit":
break;
default:
data[fields[i].id] = fields[i].value;
break;
}
}
const request = new Request("/setup/instance", {
headers: { "Content-Type": "application/json" },
method: "POST",
body: JSON.stringify(data)
});
const response = await fetch(request);
const json = await response.json();
window.location = "/";
}
</script>

View file

@ -0,0 +1,29 @@
<!doctypehtml>
<title></title>
<link href=https://error.checksum.fail/css/12391289038129038.css rel=stylesheet>
<link href=https://linusg.github.io/serenityos-emoji-font/SerenityOS-Emoji.css rel=stylesheet>
<style>
* {
font-family: "SerenityOS Emoji", "TempleOS", -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell, Ubuntu, roboto, noto, arial, sans-serif
}
.pix {
image-rendering: pixelated
}
</style>
<div class=page-container>
<div class=page-header></div>
<div class=page-content>
<br>
<br>
<center><font style="color: #fff">Loading Statuses...</font><img class="text-cursor"></center>
<br>
</div>
<div class=page-footer>This server is powered by <a href=https://slon-project.org>Slon</a>, a
Mastodon-compatible fediverse instance for TempleOS, written in HolyC</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
<script>dayjs.extend(window.dayjs_plugin_relativeTime)</script>
<script src=https://error.checksum.fail/js/header.js></script>
<script src=https://error.checksum.fail/js/12389290138092193.js></script>

3
Slon/Static/js/header.js Normal file
View file

@ -0,0 +1,3 @@
let lastFrameTime=0,fps=0;function calculateFPS(){requestAnimationFrame(()=>{const a=performance.now();fps=1E3/(a-lastFrameTime);lastFrameTime=a;calculateFPS()})}
function updatePageHeader(){var a=new Date,c=a.toString().substring(0,3);const d=String(a.getMonth()+1).padStart(2,"0"),e=String(a.getDate()).padStart(2,"0");a=a.toString().substring(16,24);const f="CPU <b>"+(10*Math.random()).toFixed(0)+"</b>";if(navigator.userAgent.toLowerCase().includes("firefox"))var b=3735929054;else b=performance.memory,b=b.jsHeapSizeLimit-b.usedJSHeapSize;c=c+" "+d+"/"+e+" "+a+" FPS:"+fps.toFixed(0)+" Mem:"+b.toString(16).toUpperCase()+" "+f;document.getElementsByClassName("page-header")[0].innerHTML=
c}calculateFPS();updatePageHeader();setInterval(updatePageHeader,300);

132
Slon/Static/js/statuses.js Normal file
View file

@ -0,0 +1,132 @@
function updateStatusContainers() {
let els = document.getElementsByClassName('status-container');
for (var n = 0; n < els.length; n++) {
let pc = els[n];
let post_html = ""
let horizontal_fill_count = ((pc.offsetWidth - 32) / 16);
let url = pc.getElementsByTagName('url')[0].textContent;
let marqueeHref = url == "Unnamed Task" ? window.location : url;
post_html += "<b>\u2554";
for (var i = 0; i < (horizontal_fill_count / 2) - ((240 / 16) / 2); i++) {
post_html += "\u2550";
}
post_html += "<a href=\"" + marqueeHref + "\"><marquee scrollamount=16>" + url + "...</marquee></a>";
for (var i = 0; i < (horizontal_fill_count / 2) - ((240 / 16) / 2) - 2; i++) {
post_html += "\u2550";
}
post_html += "[X]\u2557<br></b>";
let height = Math.ceil(pc.getElementsByClassName('status-content')[0].offsetHeight / 16);
let desc = "Term";
for (var y = 0; y < height; y++) {
let ch = y < 4 ? desc[y] : "\u2551";
post_html += "<b>" + ch + "<div style=\"display: inline-block; height: 16px; width: " + ((horizontal_fill_count + 1) * 16).toString() + "px\"></div>\u2551</b><br>";
}
post_html += "<b>\u255a";
for (var i = 0; i < (horizontal_fill_count + 1); i++) {
post_html += "\u2550";
}
post_html += "\u255d<br></b>";
pc.innerHTML += post_html;
pc.style.display = "inline";
}
}
function smolDate(a){return a.split(" ago")[0].replace("a ","1").replace("an ","1").replace("days","d").replace("day","d").replace("hours","h").replace("hour","h").replace("minutes","m").replace("minute","m").replace("seconds","s").replace("second","s").replace("few","").replace(" ","")};
function updateStatuses(user, statuses) {
let pageContent = document.getElementsByClassName("page-content")[0];
let elements = document.createElement('div');
let container = document.createElement('div');
container.className = "status-container";
// Render user profile
{
let content = document.createElement('div');
content.className = "status-content";
let content_html = "";
content_html += "<div class=status-avatar style=margin:16px;background:url(" + user["icon"]["url"] + ");width:72px;height:72px;background-size:contain;background-repeat:no-repeat></div>";
content_html += "<div class=status-header>" + user["preferredUsername"] + "<br><a href=" + user["url"] + ">@" + user["preferredUsername"] + "@" + location.host + "</a></div>"
content_html += "<div class=status-text><font style=color:#0>" + user["summary"] + "</font></div>";
content_html += "<div class=status-text>Joined " + new Date(Date.parse(user["published"])).toString().substr(0,15) + "</div>";
content.innerHTML = content_html;
let url = document.createElement('url');
url.textContent = window.location;
let menuImg = document.createElement('img');
menuImg.className = "text-menu";
content.appendChild(menuImg);
container.appendChild(content);
container.appendChild(url);
elements.appendChild(container);
let spacer = document.createElement('div');
spacer.style.height = "16px";
elements.appendChild(spacer);
}
elements.className = "statuses";
statuses.sort((a,b) => b.id - a.id);
for (var i = 0; i < statuses.length; i++) {
let status = statuses[i];
let container = document.createElement('div');
container.className = "status-container";
let content = document.createElement('div');
content.className = "status-content";
let content_html = "";
if (status["visibility"] == "public") {
content_html += "<span title=Public>&#127758;</span> ";
}
content_html += "<span class=status-timestamp>" + smolDate(dayjs(status["created_at"]).fromNow()) + "</span><br></div>";
content_html += "<div class=status-text>" + status["content"] + "</div>";
content_html += "<span class=status-counts>&#128172; " + status["replies_count"] + " &#128257; " + status["reblogs_count"] + " &#11088; " + status["favourites_count"] + "</span>";
content_html += "<div class=status-footer>via <a href=" + status["application"]["website"] + ">" + status["application"]["name"] + "</a></div>";
content.innerHTML = content_html;
let url = document.createElement('url');
url.textContent = status["url"];
let menuImg = document.createElement('img');
menuImg.className = "text-menu";
content.appendChild(menuImg);
container.appendChild(content);
container.appendChild(url);
elements.appendChild(container);
if (i < statuses.length - 1) {
let spacer = document.createElement('div');
spacer.style.height = "16px";
elements.appendChild(spacer);
}
}
if (!statuses.length) {
let container = document.createElement('div');
container.className = "status-container";
let content = document.createElement('div');
content.className = "status-content";
let content_html = "";
content_html += "<div class=nostatus><font style=color:#0000a8>&FileRead &StatusRead &StatusPrint &ExeCmdLine <img class=text-error> No statuses found.\n " + Math.random().toFixed(6) + "s ans=0x00000000=0<br>C:/Home><img class=text-cursor></font></div>";
content.innerHTML = content_html;
let url = document.createElement('url');
url.textContent = "Unnamed Task";
let menuImg = document.createElement('img');
menuImg.className = "text-menu";
content.appendChild(menuImg);
container.appendChild(content);
container.appendChild(url);
elements.appendChild(container);
}
pageContent.innerHTML = "";
pageContent.appendChild(elements);
updateStatusContainers();
}
function getStatuses(user) {
fetch("https://error.checksum.fail/api/v1/accounts/" + user["accountId"] + "/statuses", {
method: 'GET',
headers: {'Accept': 'application/json' }
})
.then(response => response.json())
.then(data => updateStatuses(user, data));
}

View file

@ -0,0 +1,12 @@
<html>
<body>
<script src="https://cdn.jsdelivr.net/gh/IdentityModel/oidc-client-js@1.8/dist/oidc-client.min.js"></script>
<script>
var retry_count = 0, retry_timeout = 2E3; function retryVerifyAccess() { 3 < ++retry_count ? alert("Retry limit exceeded") : setTimeout(verifyAccess, retry_timeout) }
async function verifyAccess() { try { var b = localStorage.getItem("authorize"); if (null != b) { const a = await fetch("https://" + window.location.host + "/oauth/verify_access?" + b + window.location.hash.substring(1)); if (a.ok) { const c = await a.json(); window.location = c.redirect_uri } else 202 == a.status ? retryVerifyAccess() : alert("Unexpected response status: " + a.status.toString()) } } catch (a) { retryVerifyAccess() } }
var settings = { authority: "https://app.simplelogin.io/", client_id: "templeosfediverseserver-yssuxmkust", response_type: "id_token token", scope: "openid", redirect_uri: "https://" + window.location.host + "/oauth/authorize" }, mgr = new Oidc.UserManager(settings); window.location.search && localStorage.setItem("authorize", window.location.search.replace("?", "").replace("state", "client_state") + "&"); window.location.hash ? verifyAccess() : mgr.signinRedirect();
</script>
</body>
</html>

25
System/Api/Dns.HC Normal file
View file

@ -0,0 +1,25 @@
#define DNS_REQUEST_PTR 0x300010
MemSet(DNS_REQUEST_PTR, NULL, sizeof(U64));
class DnsRequest {
U64 host;
U64 pointer_to_u32;
};
U32 @dns_query(U8* host)
{
U32 res = 0;
if (!host)
return U32_MAX;
DnsRequest* request = CAlloc(sizeof(DnsRequest), Fs->code_heap);
request->host = StrNew(host, adam_task);
request->pointer_to_u32 = &res;
U64* request_ptr = DNS_REQUEST_PTR;
while (*request_ptr)
Sleep(1);
LXchgU32(request_ptr, request);
while (!res)
Sleep(1);
return res;
}

32
System/Api/Icmp.HC Normal file
View file

@ -0,0 +1,32 @@
#define ICMP_REQUEST_PTR 0x300020
MemSet(ICMP_REQUEST_PTR, NULL, sizeof(U64));
class IcmpRequest {
U64 addr;
U64 iden;
U64 seq;
U64 pointer_to_u32;
};
U32 @icmp_echo_request(U32 addr, U16 iden, U16 seq, IcmpRequest* request, I64 count)
{
U32 res = 0; // low 16 = ttl, hi 16 = payload size
request->addr = addr;
request->iden = iden;
request->seq = seq;
request->pointer_to_u32 = &res;
I64 start_jiffies = cnts.jiffies;
U64* request_ptr = ICMP_REQUEST_PTR;
if (!count)
*request_ptr = NULL;
while (*request_ptr) {
if (!(cnts.jiffies < start_jiffies + 1000))
return res;
Sleep(1);
}
LXchgU32(request_ptr, request);
while (!res && cnts.jiffies < start_jiffies + 1000)
Sleep(1);
return res;
}

9
System/Api/Ipv4.HC Normal file
View file

@ -0,0 +1,9 @@
U32 @ipv4_address(I64 o3, I64 o2, I64 o1, I64 o0)
{
U32 addr = NULL;
addr.u8[3] = o3;
addr.u8[2] = o2;
addr.u8[1] = o1;
addr.u8[0] = o0;
return addr;
}

144
System/Api/MD5.HC Normal file
View file

@ -0,0 +1,144 @@
/*
* Simple MD5 implementation
*
* https://gist.github.com/creationix/4710780
*/
U32 md5_r[64] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,
4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };
// Use binary integer part of the sines of integers (in radians) as constants//
// Initialize variables:
U32 md5_k[64] = {
0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,
0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,
0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,
0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,
0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,
0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,
0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,
0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,
0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,
0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,
0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391
};
// leftrotate function
U32 LEFTROTATE(U32 x, U32 c) { return (((x) << (c)) | ((x) >> (32 - (c)))); }
U0 md5(U8* initial_msg, U32 initial_len, U32* md5_h)
{
// These vars will contain the hash
U32 md5_h0, md5_h1, md5_h2, md5_h3;
// Message (to prepare)
U8* msg = NULL;
// Note: All variables are unsigned 32 bit and wrap modulo 2^32 when
// calculating
// r specifies the per-round shift amounts
md5_h0 = 0x67452301;
md5_h1 = 0xefcdab89;
md5_h2 = 0x98badcfe;
md5_h3 = 0x10325476;
// Pre-processing: adding a single 1 bit
// append "1" bit to message
/* Notice: the input bytes are considered as bits strings,
where the first bit is the most significant bit of the byte.[37] */
// Pre-processing: padding with zeros
// append "0" bit until message length in bit ≡ 448 (mod 512)
// append length mod (2 pow 64) to message
U32 new_len;
for (new_len = initial_len * 8 + 1; new_len % 512 != 448; new_len++)
;
new_len /= 8;
msg = CAlloc(new_len + 64, adam_task); // also appends "0" bits
// (we alloc also 64 extra bytes...)
MemCpy(msg, initial_msg, initial_len);
msg[initial_len] = 128; // write the "1" bit
U32 bits_len = 8 * initial_len; // note, we append the len
MemCpy(msg + new_len, &bits_len, 4); // in bits at the end of the buffer
// Process the message in successive 512-bit chunks:
// for each 512-bit chunk of message:
U32 offset;
for (offset = 0; offset < new_len; offset += (512 / 8)) {
// break chunk into sixteen 32-bit words w[j], 0 ≤ j ≤ 15
U32* w = (msg + offset)(U32*);
// Initialize hash value for this chunk:
U32 a = md5_h0;
U32 b = md5_h1;
U32 c = md5_h2;
U32 d = md5_h3;
// Main loop:
U32 i;
for (i = 0; i < 64; i++) {
U32 f, g;
if (i < 16) {
f = (b & c) | ((~b) & d);
g = i;
} else if (i < 32) {
f = (d & b) | ((~d) & c);
g = (5 * i + 1) % 16;
} else if (i < 48) {
f = b ^ c ^ d;
g = (3 * i + 5) % 16;
} else {
f = c ^ (b | (~d));
g = (7 * i) % 16;
}
U32 temp = d;
d = c;
c = b;
// printf("rotateLeft(%x + %x + %x + %x, %d)\n", a, f, k[i], w[g], r[i]);
b = b + LEFTROTATE((a + f + md5_k[i] + w[g]), md5_r[i]);
a = temp;
}
// Add this chunk's hash to result so far:
md5_h0 += a;
md5_h1 += b;
md5_h2 += c;
md5_h3 += d;
}
md5_h[0] = md5_h0;
md5_h[1] = md5_h1;
md5_h[2] = md5_h2;
md5_h[3] = md5_h3;
// cleanup
Free(msg);
}
U8* md5_string(U8* buf, I64 size)
{
U32 md5_h[4];
md5(buf, size, &md5_h[0]);
U8* str = CAlloc(33, adam_task);
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[0].u8[0],
md5_h[0].u8[1], md5_h[0].u8[2], md5_h[0].u8[3]);
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[1].u8[0],
md5_h[1].u8[1], md5_h[1].u8[2], md5_h[1].u8[3]);
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[2].u8[0],
md5_h[2].u8[1], md5_h[2].u8[2], md5_h[2].u8[3]);
StrPrint(str + StrLen(str), "%02x%02x%02x%02x", md5_h[3].u8[0],
md5_h[3].u8[1], md5_h[3].u8[2], md5_h[3].u8[3]);
return str;
}

32
System/Api/NetInfo.HC Normal file
View file

@ -0,0 +1,32 @@
#define NETINFO_REQUEST_PTR 0x300030
MemSet(NETINFO_REQUEST_PTR, NULL, sizeof(U64));
class NetInfoRequest {
U64 mac_address;
U64 ipv4_address;
U64 ipv4_netmask;
U64 ipv4_network;
U64 ipv4_gateway;
U64 dns_server_address;
U64 dns_server_port;
U64 rx_bytes;
U64 rx_frames;
U64 tx_bytes;
U64 tx_frames;
U64 pointer_to_u32;
};
NetInfoRequest* @net_info_request()
{
U32 res = 0;
NetInfoRequest* req = CAlloc(sizeof(NetInfoRequest), Fs->code_heap);
req->pointer_to_u32 = &res;
U64* request_ptr = NETINFO_REQUEST_PTR;
while (*request_ptr)
Sleep(1);
LXchgU32(request_ptr, req);
while (!res)
Sleep(1);
return req;
}

210
System/Api/Tcp.HC Normal file
View file

@ -0,0 +1,210 @@
#define TCP_SOCKET_REQUEST_PTR 0x300000
#define TCP_BIND_REQUEST_PTR 0x300040
#define TCP_ACCEPT_REQUEST_PTR 0x300050
MemSet(TCP_SOCKET_REQUEST_PTR, NULL, sizeof(U64));
// TcpSocket states
#define TCP_SOCKET_STATE_IDLE 0
#define TCP_SOCKET_STATE_ESTABLISHED 1
#define TCP_SOCKET_STATE_CLOSED 2
#define TCP_SOCKET_STATE_CONNECTING 4
class TcpSocket {
U64 remote_addr;
U64 remote_port;
U64 state;
U64 receive_buffer_ptr; // Pointer to receive buffer in physical memory
U64 receive_buffer_size;
U64 receive_buffer_filled; // Number of bytes Net has put into buffer
U64 receive_buffer_kick; // Net sets this to 1 when it has data available for
// us, we set back to 0 when ready to receive
U64 send_buffer_ptr;
U64 send_buffer_size;
U64 send_buffer_filled;
U64 send_buffer_kick; // We set this to 1 when we have data available to net,
// Net sets back to 0 when ready to receive
U0(*close)
();
U64(*receive)
(U64 buf, U64 length);
U0(*send)
(U64 buf, U64 length);
};
class TcpBind {
U64 port;
U64 function;
U64 response_code;
};
U8 @tcp_close_wrapper_function[16]
= { 0x55, 0x48, 0x8B, 0xEC, 0x68, 0x78,
0x56, 0x34, 0x12, 0xE8, 0x02, 0x6D,
0x02, 0x00, 0x5D, 0xC3 };
U8 @tcp_receive_wrapper_function[32] = {
0x55, 0x48, 0x8B, 0xEC, 0x56, 0x57, 0x48, 0x8B, 0x75, 0x18, 0x48,
0x8B, 0x7D, 0x10, 0x56, 0x57, 0x68, 0x78, 0x56, 0x34, 0x12, 0xE8,
0x5E, 0x62, 0x02, 0x00, 0x5F, 0x5E, 0x5D, 0xC2, 0x10, 0x00
};
U8 @tcp_send_wrapper_function[32] = {
0x55, 0x48, 0x8B, 0xEC, 0x56, 0x57, 0x48, 0x8B, 0x75, 0x18, 0x48,
0x8B, 0x7D, 0x10, 0x56, 0x57, 0x68, 0x78, 0x56, 0x34, 0x12, 0xE8,
0x5E, 0x62, 0x02, 0x00, 0x5F, 0x5E, 0x5D, 0xC2, 0x10, 0x00
};
U0 @tcp_socket_send(TcpSocket* s, U64 buf, U64 length)
{
while (s->send_buffer_kick)
Sleep(1);
U64 pos = 0;
U64 bytes_to_send = 0;
while (pos < length) {
if ((length - pos) > s->send_buffer_size)
bytes_to_send = s->send_buffer_size;
else
bytes_to_send = length - pos;
MemCpy(s->send_buffer_ptr, buf + pos, bytes_to_send);
s->send_buffer_filled = bytes_to_send;
s->send_buffer_kick = 1;
pos += bytes_to_send;
while (s->send_buffer_kick)
Sleep(1);
}
}
U64 @tcp_socket_receive(TcpSocket* s, U64 buf, U64 size)
{
s->receive_buffer_size = size;
s->receive_buffer_kick = 0;
while (!s->receive_buffer_kick) {
if (s->state == TCP_SOCKET_STATE_CLOSED)
return NULL;
Sleep(1);
}
U64 bytes_received = s->receive_buffer_filled;
if (bytes_received > 0) {
MemCpy(buf, s->receive_buffer_ptr, bytes_received);
}
return bytes_received;
}
U0 @tcp_wait_for_connection_established(TcpSocket* s)
{
while (s->state != TCP_SOCKET_STATE_ESTABLISHED)
Sleep(1);
}
U0 @tcp_socket_close(TcpSocket* s)
{
if (s->close)
Free(s->close);
if (s->receive)
Free(s->receive);
if (s->send)
Free(s->send);
s->state = TCP_SOCKET_STATE_CLOSED;
}
TcpSocket* @tcp_socket_create(U8* host, U64 port)
{
U64 addr = @dns_query(host);
TcpSocket* s = CAlloc(sizeof(TcpSocket), adam_task->code_heap);
s->remote_addr = addr;
s->remote_port = port;
U64 a;
s->close = MAlloc(16, adam_task->code_heap);
MemCpy(s->close, @tcp_close_wrapper_function, 16);
a = s->close;
a += 0x05;
MemSetU32(a, s, 1);
a = s->close;
a += 0x09;
@patch_call_rel32(a, &@tcp_socket_close);
s->receive = MAlloc(25, adam_task->code_heap);
MemCpy(s->receive, @tcp_receive_wrapper_function, 32);
a = s->receive;
a += 0x11;
MemSetU32(a, s, 1);
a = s->receive;
a += 0x15;
@patch_call_rel32(a, &@tcp_socket_receive);
s->send = MAlloc(32, adam_task->code_heap);
MemCpy(s->send, @tcp_send_wrapper_function, 32);
a = s->send;
a += 0x11;
MemSetU32(a, s, 1);
a = s->send;
a += 0x15;
@patch_call_rel32(a, &@tcp_socket_send);
U64* request_ptr = TCP_SOCKET_REQUEST_PTR;
while (*request_ptr)
Sleep(1);
LXchgU32(request_ptr, s);
return s;
}
U64 @tcp_socket_bind(U64 port, U64 function)
{
if (!port || !function)
return NULL;
TcpBind* b = CAlloc(sizeof(TcpBind), adam_task->code_heap);
b->port = port;
b->function = function; // U0 my_spawn_wrapper_function(TcpSocket* s)
U64* request_ptr = TCP_BIND_REQUEST_PTR;
while (*request_ptr)
Sleep(1);
LXchgU32(request_ptr, b);
while (*request_ptr)
Sleep(1);
U64 res = b->response_code;
Free(b);
return res;
}
TcpSocket* @tcp_socket_accept(TcpSocket* s)
{
if (!s || !s->remote_addr || !s->remote_port)
return NULL;
U64 a;
s->close = MAlloc(16, adam_task->code_heap);
MemCpy(s->close, @tcp_close_wrapper_function, 16);
a = s->close;
a += 0x05;
MemSetU32(a, s, 1);
a = s->close;
a += 0x09;
@patch_call_rel32(a, &@tcp_socket_close);
s->receive = MAlloc(25, adam_task->code_heap);
MemCpy(s->receive, @tcp_receive_wrapper_function, 32);
a = s->receive;
a += 0x11;
MemSetU32(a, s, 1);
a = s->receive;
a += 0x15;
@patch_call_rel32(a, &@tcp_socket_receive);
s->send = MAlloc(32, adam_task->code_heap);
MemCpy(s->send, @tcp_send_wrapper_function, 32);
a = s->send;
a += 0x11;
MemSetU32(a, s, 1);
a = s->send;
a += 0x15;
@patch_call_rel32(a, &@tcp_socket_send);
return s;
}

99
System/Api/Tls.HC Normal file
View file

@ -0,0 +1,99 @@
#define TLS_CONNECT_TASK_STACK_SIZE 524288
#define TLS_CLIENT_MESSAGE_BUFFER_SIZE 0xFFFF
class TlsSocket : TcpSocket {
U64 ctx;
U8 client_message[TLS_CLIENT_MESSAGE_BUFFER_SIZE];
};
U0 @tls_send_pending(TlsSocket* s)
{
U32 out_buffer_len = 0;
U8* out_buffer = @tls_get_write_buffer(s->ctx, &out_buffer_len);
if (out_buffer && out_buffer_len) {
@tcp_socket_send(s, out_buffer, out_buffer_len);
@tls_buffer_clear(s->ctx);
}
}
U0 @tls_socket_send(TlsSocket* s, U64 buf, U64 size)
{
@tls_write(s->ctx, buf, size);
@tls_send_pending(s);
}
U64 @tls_socket_receive(TlsSocket* s, U8* buf, I64 size)
{
I64 len = @tcp_socket_receive(s, s->client_message, TLS_CLIENT_MESSAGE_BUFFER_SIZE);
if (len) {
@tls_consume_stream(s->ctx, s->client_message, len, NULL);
@tls_send_pending(s);
}
return @tls_read(s->ctx, buf, size);
}
U0 @tls12_connect(TlsSocket* s)
{
I64 len;
@tls_client_connect(s->ctx);
@tls_send_pending(s);
while (!@tls_established(s->ctx)) {
len = @tcp_socket_receive(s, &s->client_message, TLS_CLIENT_MESSAGE_BUFFER_SIZE);
if (len) {
@tls_consume_stream(s->ctx, &s->client_message, len, NULL);
@tls_send_pending(s);
}
Sleep(1);
}
}
TlsSocket* @tls_socket_create(U8* server_name, U64 port = 443)
{
U64 addr = @dns_query(server_name);
TlsSocket* s = CAlloc(sizeof(TlsSocket), adam_task->code_heap);
s->remote_addr = addr;
s->remote_port = port;
U64 a;
s->close = MAlloc(16, adam_task->code_heap);
MemCpy(s->close, @tcp_close_wrapper_function, 16);
a = s->close;
a += 0x05;
MemSetU32(a, s, 1);
a = s->close;
a += 0x09;
@patch_call_rel32(a, &@tcp_socket_close);
s->receive = MAlloc(25, adam_task->code_heap);
MemCpy(s->receive, @tcp_receive_wrapper_function, 32);
a = s->receive;
a += 0x11;
MemSetU32(a, s, 1);
a = s->receive;
a += 0x15;
@patch_call_rel32(a, &@tls_socket_receive);
s->send = MAlloc(32, adam_task->code_heap);
MemCpy(s->send, @tcp_send_wrapper_function, 32);
a = s->send;
a += 0x11;
MemSetU32(a, s, 1);
a = s->send;
a += 0x15;
@patch_call_rel32(a, &@tls_socket_send);
U64* request_ptr = TCP_SOCKET_REQUEST_PTR;
while (*request_ptr)
Sleep(1);
LXchgU32(request_ptr, s);
while (s->state != TCP_SOCKET_STATE_ESTABLISHED)
Sleep(1);
s->ctx = @tls_create_context(0, TLS_V12);
@tls_sni_set(s->ctx, StrNew(server_name, adam_task->code_heap));
Spawn(&@tls12_connect, s, , , , TLS_CONNECT_TASK_STACK_SIZE);
return s;
}

10
System/Config/Net.json Normal file
View file

@ -0,0 +1,10 @@
{
"tcpip.ipv4_address": "10.20.0.10",
"tcpip.ipv4_netmask": "255.255.255.0",
"tcpip.ipv4_network": "10.20.0.0",
"tcpip.ipv4_gateway": "10.20.0.254",
"tcpip.ipv4_dns_server_address": "8.8.8.8",
"tcpip.ipv4_dns_server_port": "53",
"tcpip.mss_size": "1360",
"eof": "eof"
}

View file

@ -0,0 +1,473 @@
// Virtio.HC
//
// PCI virtio I/O registers.
//
#define VIRTIO_PCI_HOST_FEATURES 0 // Features supported by the host
#define VIRTIO_PCI_GUEST_FEATURES 4 // Features activated by the guest
#define VIRTIO_PCI_QUEUE_PFN 8 // PFN for the currently selected queue
#define VIRTIO_PCI_QUEUE_SIZE 12 // Queue size for the currently selected queue
#define VIRTIO_PCI_QUEUE_SEL 14 // Queue selector
#define VIRTIO_PCI_QUEUE_NOTIFY 16 // Queue notifier
#define VIRTIO_PCI_STATUS 18 // Device status register
#define VIRTIO_PCI_ISR 19 // Interrupt status register
#define VIRTIO_PCI_CONFIG 20 // Configuration data block
//
// PCI virtio status register bits
//
#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
#define VIRTIO_CONFIG_S_DRIVER 2
#define VIRTIO_CONFIG_S_DRIVER_OK 4
#define VIRTIO_CONFIG_S_FAILED 0x80
//
// Ring descriptor flags
//
#define VRING_DESC_F_NEXT 1 // Buffer continues via the next field
#define VRING_DESC_F_WRITE 2 // Buffer is write-only (otherwise read-only)
#define VRING_DESC_F_INDIRECT 4 // Buffer contains a list of buffer descriptors
class @virtio_queue_buf
{
U64 address;
U32 length;
U16 flags;
U16 next;
};
class @virtio_avail
{
U16 flags;
U16 index;
U16 ring[256];
U16 int_index;
};
class @virtio_used_item
{
U32 index;
U32 length;
};
class @virtio_used
{
U16 flags;
U16 index;
@virtio_used_item ring[256];
U16 int_index;
};
class @virtio_queue
{
@virtio_queue_buf buffers[256];
@virtio_avail available;
U8 padding[3578];
@virtio_used used;
};
class @virtio_avail_buf
{
U32 index;
U64 address;
U32 length;
};
class @virtio_buf_info
{
U8* buffer;
U64 size;
U8 flags;
// If the user wants to keep same buffer as passed in this struct, use "true".
// otherwise, the supplied buffer will be copied in the queues' buffer
Bool copy;
};
// Virtio-blk.HC
#define BDT_VIRTIO_BLK 10
#define VIRTIO_BLK_T_IN 0
#define VIRTIO_BLK_T_OUT 1
#define VIRTIO_BLK_T_FLUSH 4
#define VIRTIO_BLK_MAX_BLK 0x400000 // Limit blkdev to 2G max, set to NULL to use entire disk (not recommended for RedSea)
class @virtio_blk
{
U16 port;
U32 blks;
@virtio_queue* vq;
I64 vq_size;
I64 vq_index;
U8 status;
};
class @virtio_blk_request
{
U32 type;
U32 priority;
U64 sector;
};
@virtio_blk virtio_blk;
MemSet(&virtio_blk, 0, sizeof(@virtio_blk));
I64 VirtioBlkInit()
{
I64 j;
// Scan for device
j = PCIClassFind(0x010000, 0);
if (j < 0) {
"\n[virtio-blk] No device found\n";
return -1;
}
virtio_blk.port = PCIReadU32(j.u8[2],
j.u8[1], j.u8[0], 0x10)
& 0xFFFFFFFC;
virtio_blk.blks = InU32(virtio_blk.port + VIRTIO_PCI_CONFIG);
// Reset Device
OutU8(virtio_blk.port + VIRTIO_PCI_STATUS, 0);
// Found Driver
OutU8(virtio_blk.port + VIRTIO_PCI_STATUS, InU8(virtio_blk.port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_ACKNOWLEDGE | VIRTIO_CONFIG_S_DRIVER);
// Set up virt queue
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_SEL, 0);
virtio_blk.vq_size = InU16(virtio_blk.port + VIRTIO_PCI_QUEUE_SIZE); // 256
virtio_blk.vq = CAllocAligned(sizeof(@virtio_queue), 4096, adam_task->code_heap);
OutU32(virtio_blk.port + VIRTIO_PCI_QUEUE_PFN, virtio_blk.vq / 4096);
// Init OK
OutU8(virtio_blk.port + VIRTIO_PCI_STATUS, InU8(virtio_blk.port + VIRTIO_PCI_STATUS) | VIRTIO_CONFIG_S_DRIVER_OK);
virtio_blk.vq_index = 0;
}
// DskVIO.HC
U0 VIOFlush()
{
I64 j;
I64 vq_idx;
@virtio_blk_request* brq = CAlloc(sizeof(@virtio_blk_request), adam_task);
brq->type = VIRTIO_BLK_T_FLUSH;
brq->sector = NULL;
vq_idx = virtio_blk.vq->available.index % 256;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].address = brq;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].length = sizeof(@virtio_blk_request);
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].flags = VRING_DESC_F_NEXT;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].next = (virtio_blk.vq_index + 1) % 256;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].address = &virtio_blk.status;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].length = 1;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].flags = VRING_DESC_F_WRITE;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].next = 0;
virtio_blk.vq->available.ring[vq_idx] = virtio_blk.vq_index % 256;
virtio_blk.vq_index += 2;
j = virtio_blk.vq->used.index;
virtio_blk.vq->available.index++;
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
while (j == virtio_blk.vq->used.index) {
Yield;
}
Free(brq);
}
Bool VIORBlks(CDrv* dv, U8* buf, I64 blk, I64 cnt)
{
no_warn dv;
I64 i, j;
I64 vq_idx;
U64 addr;
@virtio_blk_request* brq = CAlloc(sizeof(@virtio_blk_request), adam_task);
for (i = 0; i < cnt; i++) {
brq->type = VIRTIO_BLK_T_IN;
brq->sector = blk + i;
vq_idx = virtio_blk.vq->available.index % 256;
addr = buf + (BLK_SIZE * i);
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].address = brq;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].length = sizeof(@virtio_blk_request);
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].flags = VRING_DESC_F_NEXT;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].next = (virtio_blk.vq_index + 1) % 256;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].address = addr;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].length = BLK_SIZE;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].flags = VRING_DESC_F_WRITE | VRING_DESC_F_NEXT;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].next = (virtio_blk.vq_index + 2) % 256;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].address = &virtio_blk.status;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].length = 1;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].flags = VRING_DESC_F_WRITE;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].next = 0;
virtio_blk.vq->available.ring[vq_idx] = virtio_blk.vq_index % 256;
virtio_blk.vq_index += 3;
j = virtio_blk.vq->used.index;
virtio_blk.vq->available.index++;
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
while (j == virtio_blk.vq->used.index) {
Yield;
}
}
Free(brq);
return TRUE;
}
Bool VIOWBlks(CDrv* dv, U8* buf, I64 blk, I64 cnt)
{
no_warn dv;
I64 i, j;
I64 vq_idx;
U64 addr;
@virtio_blk_request* brq = CAlloc(sizeof(@virtio_blk_request), adam_task);
for (i = 0; i < cnt; i++) {
brq->type = VIRTIO_BLK_T_OUT;
brq->sector = blk + i;
vq_idx = virtio_blk.vq->available.index % 256;
addr = buf + (BLK_SIZE * i);
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].address = brq;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].length = sizeof(@virtio_blk_request);
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].flags = VRING_DESC_F_NEXT;
virtio_blk.vq->buffers[virtio_blk.vq_index % 256].next = (virtio_blk.vq_index + 1) % 256;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].address = addr;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].length = BLK_SIZE;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].flags = VRING_DESC_F_NEXT;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 1) % 256].next = (virtio_blk.vq_index + 2) % 256;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].address = &virtio_blk.status;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].length = 1;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].flags = VRING_DESC_F_WRITE;
virtio_blk.vq->buffers[(virtio_blk.vq_index + 2) % 256].next = 0;
virtio_blk.vq->available.ring[vq_idx] = virtio_blk.vq_index % 256;
virtio_blk.vq_index += 3;
j = virtio_blk.vq->used.index;
virtio_blk.vq->available.index++;
OutU16(virtio_blk.port + VIRTIO_PCI_QUEUE_NOTIFY, 0);
while (j == virtio_blk.vq->used.index) {
Yield;
}
}
Free(brq);
VIOFlush;
return TRUE;
}
U0 RedSeaTryInit(CDrv* dv)
{
CRedSeaBoot br;
Bool unlock;
try {
unlock = DrvLock(dv);
BlkRead(dv, &br, dv->drv_offset, 1);
if (br.signature != MBR_PT_REDSEA || br.signature2 != 0xAA55)
return;
dv->fs_type = FSt_REDSEA;
CallExtStr("RedSeaFreeFreeLst", dv);
dv->spc = 1;
dv->size = br.sects;
dv->data_area = dv->drv_offset + br.bitmap_sects;
dv->root_clus = br.root_clus;
dv->fat1 = dv->fat2 = dv->drv_offset + 1;
CallExtStr("DrvFATBlkAlloc", dv);
if (unlock)
DrvUnlock(dv);
} catch if (unlock)
DrvUnlock(dv);
}
U8 MountVirtioBlk()
{ // Mount Virtio-blk device
CDrv* dv = DrvMakeFreeSlot(DrvNextFreeLet('A'));
CBlkDev* bd = BlkDevNextFreeSlot(dv->drv_let, BDT_RAM);
CRedSeaBoot* bs = CAlloc(BLK_SIZE, adam_task);
bd->max_blk = 512;
BlkDevAdd(bd, , TRUE, TRUE);
bd->type = BDT_VIRTIO_BLK;
if (VIRTIO_BLK_MAX_BLK) {
bd->max_blk = Min(VIRTIO_BLK_MAX_BLK, virtio_blk.blks);
} else {
bd->max_blk = virtio_blk.blks;
}
Free(bd->RAM_dsk);
dv->size = bd->max_blk + 1 - bd->drv_offset;
VIORBlks(dv, bs, 0, 1);
dv->root_clus = bs->root_clus;
dv->data_area = bs->bitmap_sects;
dv->next_free = NULL;
dv->last_free = NULL;
Free(bs);
RedSeaTryInit(dv);
return dv->drv_let;
}
// DskBlk2.HC
Bool BlkRead2(CDrv* dv, U8* buf, I64 blk, I64 cnt)
{ // Read blk cnt from Drv to buf.
Bool res = TRUE, unlock;
CBlkDev* bd = dv->bd;
if (cnt <= 0)
return TRUE;
DrvChk(dv);
try {
unlock = DrvLock(dv);
CallExtStr("BlkDevInit", bd);
if (dv->drv_offset && blk < dv->drv_offset || blk + cnt > dv->drv_offset + dv->size)
throw('Drv');
if (bd->flags & BDF_READ_CACHE)
CallExtStr("RCache", dv, &buf, &blk, &cnt);
if (cnt > 0) {
switch (bd->type) {
case BDT_RAM:
MemCpy(buf, bd->RAM_dsk + blk << BLK_SIZE_BITS, cnt << BLK_SIZE_BITS);
break;
case BDT_ISO_FILE_READ:
case BDT_ISO_FILE_WRITE:
FBlkRead(bd->file_dsk, buf, blk, cnt);
break;
case BDT_ATA:
case BDT_ATAPI:
res = CallExtStr("ATARBlks", dv, buf, blk, cnt);
break;
case BDT_VIRTIO_BLK:
res = VIORBlks(dv, buf, blk, cnt);
break;
}
bd->last_time = tS;
if (bd->flags & BDF_READ_CACHE)
CallExtStr("DskCacheAdd", dv, buf, blk, cnt);
}
if (unlock)
DrvUnlock(dv);
} catch if (unlock)
DrvUnlock(dv);
return res;
}
Bool BlkWrite2(CDrv* dv, U8* buf, I64 blk, I64 cnt)
{ // Write blk cnt from buf to Drv.
Bool res = TRUE, unlock;
CBlkDev* bd = dv->bd;
if (cnt <= 0)
return TRUE;
DrvChk(dv);
try {
unlock = DrvLock(dv);
CallExtStr("BlkDevInit", bd);
if (bd->flags & BDF_READ_ONLY && !(bd->flags & BDF_READ_ONLY_OVERRIDE))
throw('BlkDev');
if (dv->drv_offset && blk < dv->drv_offset || blk + cnt > dv->drv_offset + dv->size)
throw('Drv');
if (cnt > 0) {
switch (bd->type) {
case BDT_RAM:
MemCpy(bd->RAM_dsk + blk << BLK_SIZE_BITS, buf, cnt << BLK_SIZE_BITS);
break;
case BDT_ISO_FILE_READ:
case BDT_ISO_FILE_WRITE:
FBlkWrite(bd->file_dsk, buf, blk, cnt);
break;
case BDT_ATA:
case BDT_ATAPI:
res = CallExtStr("ATAWBlks", dv, buf, blk, cnt);
break;
case BDT_VIRTIO_BLK:
res = VIOWBlks(dv, buf, blk, cnt);
break;
}
bd->last_time = tS;
if (bd->flags & BDF_READ_CACHE)
CallExtStr("DskCacheAdd", dv, buf, blk, cnt);
}
if (unlock)
DrvUnlock(dv);
} catch if (unlock)
DrvUnlock(dv);
return res;
}
@patch_jmp_rel32(&BlkRead, &BlkRead2);
@patch_jmp_rel32(&BlkWrite, &BlkWrite2);
// DskBlkDev2.HC
CBlkDev* BlkDevChk2(CBlkDev* bd, Bool except = TRUE)
{ // Check for valid BlkDev. Throw exception.
if (bd->type == BDT_VIRTIO_BLK)
return bd;
if (!bd || bd->bd_signature != BD_SIGNATURE_VAL || !(BDT_NULL < bd->type < BDT_TYPES_NUM)) {
if (except)
throw('BlkDev');
else
return NULL;
} else
return bd;
}
@patch_jmp_rel32(&BlkDevChk, &BlkDevChk2);
// DskDrv2.HC
DefineLstLoad("ST_BLKDEV_TYPES2",
"NULL\0RAM\0ATA\0FILE_READ\0FILE_WRITE\0ATAPI\0NULL\0NULL\0NULL\0NULL\0VIRTIO\0");
U8 DrvTextAttrGet2(U8 drv_let = 0)
{ // Get color of drive.
U8* blkdev_text_attr2 = blkdev_text_attr;
U8* drv_text_attr2 = drv_text_attr;
I64 dta_size = 3;
drv_let = Let2Let(drv_let);
if (drv_let == 'A')
return BLACK << 4 | WHITE;
if ('A' <= drv_let <= 'Z')
return blkdev_text_attr2[Let2BlkDevType(drv_let)] << 4 | drv_text_attr2[drv_let % dta_size];
else
return BLACK << 4 | WHITE;
}
U0 DrvRep2()
{ // Drive report.
CDrv* dv;
CBlkDev* bd;
I64 ch, i, drv_let, attr;
U8* st;
"\nDefined Drives:\n";
for (i = 0, dv = blkdev.drvs; i < DRVS_NUM; i++, dv++) {
if (dv->dv_signature == DRV_SIGNATURE_VAL) {
bd = dv->bd;
drv_let = Drv2Let(dv);
if (Bt(&dv->fs_type, FStf_DISABLE))
ch = '-';
else if (drv_let == blkdev.boot_drv_let)
ch = ':';
else
ch = '+';
attr = DrvTextAttrGet(drv_let);
"\dFG,%d\d\dBG,%d\d%C %-8Z %-10Z %04X %04X %02X\n",
attr & 15, attr >> 4, drv_let, dv->fs_type &FSG_TYPE_MASK, "ST_DRV_TYPES",
bd->type, "ST_BLKDEV_TYPES2", bd->base0, bd->base1, bd->unit;
if (st = DrvModelNum(drv_let)) {
"Model#:%s\n", st;
Free(st);
}
if (st = DrvSerialNum(drv_let)) {
"Serial#:%s\n", st;
Free(st);
}
if (bd->type == BDT_ISO_FILE_READ || bd->type == BDT_ISO_FILE_WRITE)
"File=\"%s\"\n", bd->file_dsk_name;
"%016X-%016X\n\dFG\d\dBG\d", dv->drv_offset, dv->drv_offset + dv->size - 1;
}
}
"Home Dir:\"%s\"\n", blkdev.home_dir;
}
@patch_jmp_rel32(&DrvTextAttrGet, &DrvTextAttrGet2);
@patch_jmp_rel32(&DrvRep, &DrvRep2);
VirtioBlkInit;
MountVirtioBlk;
if (Let2Drv('A', 0) && !Let2Drv('A')->root_clus) {
"[virtio-blk] RedSea filesystem not initialized, formatting.\n";
Fmt('A', , FALSE, FSt_REDSEA);
Cd("M:/System/");
}

51
System/FFI/Base.HC Normal file
View file

@ -0,0 +1,51 @@
#define PUSH_SYSV_REGS \
asm {PUSH RCX PUSH RDX PUSH RBX PUSH RBP PUSH RSI PUSH RDI PUSH R8 PUSH R9 PUSH \
R10 PUSH R11 PUSH R12 PUSH R13 PUSH R14 PUSH R15}
#define POP_SYSV_REGS \
p0 = p0; \
p1 = p1; \
p2 = p2; \
p3 = p3; \
p4 = p4; \
p5 = p5; \
asm {POP R15 POP R14 POP R13 POP R12 POP R11 POP R10 POP R9 POP R8 POP RDI POP \
RSI POP RBP POP RBX POP RDX POP RCX}
#define GET_SYSV_ARGS \
asm {PUSH R9 PUSH R8 PUSH RCX PUSH RDX PUSH RSI PUSH RDI} \
I64 reg RDI p0; \
I64 reg RSI p1; \
I64 reg RDX p2; \
I64 reg RCX p3; \
I64 reg R8 p4; \
I64 reg R9 p5; \
asm {POP RDI POP RSI POP RDX POP RCX POP R8 POP R9}
#define MOV_ANS_RAX asm { MOV[&ans], RAX }
#define MOV_PARAM0_RDI asm {MOV [&param0], RDI}
I64 param0;
I64 elf_argc;
U8** elf_argv;
asm {
_ELF_CALL::
PUSH RBP
MOV RBP,RSP
MOV RAX,U64 SF_ARG1[RBP]
MOV RDI,U64 SF_ARG2[RBP]
MOV RSI,U64 SF_ARG3[RBP]
TEST RAX,RAX
JZ @@05
CALL RAX
@@05: POP RBP
RET1 8
}
U0 _main()
{
MOV_PARAM0_RDI
CallInd(_ELF_CALL, param0, elf_argc, elf_argv);
UserTaskCont;
}
U0 _exit() { UserTaskCont; }

301
System/FFI/ELF64.HC Normal file
View file

@ -0,0 +1,301 @@
#define EI_NIDENT 16
#define EM_X86_64 0x3E
#define ET_EXEC 2
#define ET_DYN 3
U0 @elf64_debug_print(U8 fmt, ...)
{
// FIXME: Remove unnecessary debug_print statements and PrintErr for errors.
no_warn fmt, argc, argv;
}
class Elf64_Ehdr {
U8 e_ident[EI_NIDENT]; /* Magic number and other info */
U16 e_type; /* Object file type */
U16 e_machine; /* Architecture */
U32 e_version; /* Object file version */
U64 e_entry; /* Entry point virtual address */
U64 e_phoff; /* Program header table file offset */
U64 e_shoff; /* Section header table file offset */
U32 e_flags; /* Processor-specific flags */
U16 e_ehsize; /* ELF header size in bytes */
U16 e_phentsize; /* Program header table entry size */
U16 e_phnum; /* Program header table entry count */
U16 e_shentsize; /* Section header table entry size */
U16 e_shnum; /* Section header table entry count */
U16 e_shstrndx; /* Section header string table index */
};
class Elf64_Shdr {
U32 sh_name; /* Section name (string tbl index) */
U32 sh_type; /* Section type */
U64 sh_flags; /* Section flags */
U64 sh_addr; /* Section virtual addr at execution */
U64 sh_offset; /* Section file offset */
U64 sh_size; /* Section size in bytes */
U32 sh_link; /* Link to another section */
U32 sh_info; /* Additional section information */
U64 sh_addralign; /* Section alignment */
U64 sh_entsize; /* Entry size if section holds table */
};
class Elf64_Sym {
U32 st_name; /* Symbol name (string tbl index) */
U8 st_info; /* Symbol type and binding */
U8 st_other; /* Symbol visibility */
U16 st_shndx; /* Section index */
U64 st_value; /* Symbol value */
U64 st_size; /* Symbol size */
};
class PLT_entry {
U8 pad[0x10];
};
class RELA_entry {
U64 r_offset;
U64 r_info;
I64 r_addend;
};
class Elf {
union {
U8* u8;
Elf64_Ehdr* ehdr;
} I64 size;
U8* dynstr;
Elf64_Sym* dynsym;
PLT_entry* plt;
RELA_entry* rela_dyn;
RELA_entry* rela_plt;
Elf64_Sym* strtab;
Elf64_Sym* symtab;
I64 rela_dyn_size;
I64 rela_plt_size;
I64 strtab_size;
I64 symtab_size;
};
U0(*_start)
();
U0 unimplemented_symbol()
{
I32 s = 0xDEADF00D;
PrintWarn("Unimplemented symbol: %s\n", s);
Dbg;
while (1)
Sleep(1);
}
Bool is_valid_elf(Elf* elf)
{
Bool res = TRUE;
if (MemCmp(elf->u8 + 1, "ELF", 3)) {
@elf64_debug_print("Invalid signature (not ELF).\n");
res = FALSE;
}
if (elf->ehdr->e_type != ET_EXEC && elf->ehdr->e_type != ET_DYN) {
@elf64_debug_print("Invalid object file type.\n");
res = FALSE;
}
if (elf->ehdr->e_machine != EM_X86_64) {
@elf64_debug_print("Invalid architecture.\n");
res = FALSE;
}
return res;
}
U0 process_elf_section_header_table(Elf* elf)
{
Elf64_Shdr* shdr = elf->u8 + elf->ehdr->e_shoff;
Elf64_Shdr* shdr_shstrtab = shdr + elf->ehdr->e_shstrndx;
U8* shstrtab = elf->u8 + shdr_shstrtab->sh_offset;
I64 i = 0;
while (i < elf->ehdr->e_shnum) {
if (!StrCmp(shstrtab + shdr->sh_name, ".symtab")) {
@elf64_debug_print("found symtab at 0x%08x, size = %d\n", shdr->sh_offset,
shdr->sh_size);
elf->symtab = elf->u8 + shdr->sh_offset;
elf->symtab_size = shdr->sh_size;
}
if (!StrCmp(shstrtab + shdr->sh_name, ".strtab")) {
@elf64_debug_print("found strtab at 0x%08x, size = %d\n", shdr->sh_offset,
shdr->sh_size);
elf->strtab = elf->u8 + shdr->sh_offset;
elf->strtab_size = shdr->sh_size;
}
if (shdr->sh_addr) {
MemCpy(shdr->sh_addr, elf->u8 + shdr->sh_offset, shdr->sh_size);
if (!StrCmp(shstrtab + shdr->sh_name, ".dynstr"))
elf->dynstr = shdr->sh_addr;
if (!StrCmp(shstrtab + shdr->sh_name, ".dynsym"))
elf->dynsym = shdr->sh_addr;
if (!StrCmp(shstrtab + shdr->sh_name, ".plt"))
elf->plt = shdr->sh_addr;
if (!StrCmp(shstrtab + shdr->sh_name, ".rela.dyn")) {
elf->rela_dyn = shdr->sh_addr;
elf->rela_dyn_size = shdr->sh_size / shdr->sh_entsize;
}
if (!StrCmp(shstrtab + shdr->sh_name, ".rela.plt")) {
elf->rela_plt = shdr->sh_addr;
elf->rela_plt_size = shdr->sh_size / shdr->sh_entsize;
}
if (!StrCmp(shstrtab + shdr->sh_name, ".bss") || !StrCmp(shstrtab + shdr->sh_name, ".tbss")) {
MemSet(shdr->sh_addr, NULL, shdr->sh_size);
@elf64_debug_print(
"Zeroed out section '%s' at physical address 0x%06x, size = %d bytes\n",
shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
} else
@elf64_debug_print(
"MemCpy section '%s' to physical address 0x%06x, size = %d bytes\n",
shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
if (!StrCmp(shstrtab + shdr->sh_name, ".bss")) {
MemSet(shdr->sh_addr, NULL, shdr->sh_size);
@elf64_debug_print("MemSet section '%s' at physical address 0x%06x to NULL, "
"size = %d bytes\n",
shstrtab + shdr->sh_name, shdr->sh_addr, shdr->sh_size);
}
}
shdr++;
i++;
}
}
U0 process_elf_rela_dyn_entries(Elf* elf)
{
I64 i;
U8* entry_name;
RELA_entry* rela_dyn = elf->rela_dyn;
for (i = 0; i < elf->rela_dyn_size; i++) {
entry_name = elf->dynstr + elf->dynsym[(rela_dyn->r_info >> 32)].st_name;
@elf64_debug_print("rela_dyn->r_offset = %08x\n", rela_dyn->r_offset);
@elf64_debug_print("entry name = '%s'\n", entry_name);
if (!StrCmp(entry_name, "__libc_start_main")) {
*(rela_dyn->r_offset)(U64*) = &_main;
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: &_main\n",
entry_name);
}
if (!StrCmp(entry_name, "stdin")) {
*(rela_dyn->r_offset)(U64*) = 0;
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 0);
}
if (!StrCmp(entry_name, "stdout")) {
*(rela_dyn->r_offset)(U64*) = 1;
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 1);
}
if (!StrCmp(entry_name, "stderr")) {
*(rela_dyn->r_offset)(U64*) = 2;
@elf64_debug_print("Set value for .rela.dyn entry '%s' to: %d\n", entry_name, 2);
}
rela_dyn++;
}
}
CHashClass* get_symbol_hash_entry(U8* entry_name)
{
I64 i;
CHashSrcSym* sym;
CHashTable* tbl = Fs->hash_table;
while (tbl) {
for (i = 0; i < tbl->mask; i++) {
sym = tbl->body[i];
while (sym) {
if (sym->type == HTT_CLASS)
if (!StrCmp(sym->str, entry_name))
return sym;
sym = sym->next;
}
}
tbl = tbl->next;
}
return NULL;
}
U64 get_symbol_address(U8* entry_name)
{
CHash* h = HashFind(entry_name, Fs->hash_table, Fs->hash_table->mask);
if (!h)
return NULL;
switch (h->type) {
case HTT_GLBL_VAR:
return h(CHashGlblVar*)->data_addr;
break;
case HTT_FUN:
return h(CHashFun*)->exe_addr;
break;
default:
return NULL;
break;
}
return NULL;
}
U0 process_elf_rela_plt_entries(Elf* elf)
{
I64 i;
U32 handler;
U32* patch;
U8* entry_name;
Bool symbol_exists;
PLT_entry* plt = elf->plt;
RELA_entry* rela_plt = elf->rela_plt;
plt++;
for (i = 0; i < elf->rela_plt_size; i++) {
symbol_exists = FALSE;
entry_name = elf->dynstr + elf->dynsym[(rela_plt->r_info >> 32)].st_name;
handler = MAlloc(sizeof(unimplemented_symbol), adam_task->code_heap);
MemCpy(handler, &unimplemented_symbol, sizeof(unimplemented_symbol));
patch = handler + 0x0A;
*patch = entry_name;
@patch_jmp_rel32(plt, handler);
@patch_call_rel32(handler + 0x16, &PrintErr);
//@patch_call_rel32(handler + 0x21, &_exit);
if (!StrCmp(entry_name, "__libc_start_main")) {
symbol_exists = TRUE;
@patch_jmp_rel32(plt, &_main);
@elf64_debug_print("Set value for .rela.plt entry '%s' to &_main\n", entry_name);
}
if (get_symbol_address(entry_name)) {
symbol_exists = TRUE;
@patch_jmp_rel32(plt, get_symbol_address(entry_name));
@elf64_debug_print("Set value for .rela.plt entry '%s' to &%s\n", entry_name,
entry_name);
}
if (!symbol_exists)
@elf64_debug_print(
"Set value for .rela.plt entry '%s' to &unimplemented_symbol\n",
entry_name);
rela_plt++;
plt++;
}
}
U0 load_elf(...)
{
if (argc < 1) {
PrintErr("Not enough arguments.\n");
return;
}
if (!FileFind(argv[0])) {
PrintErr("File not found: %s\n", argv[0]);
return;
}
Elf elf;
elf.u8 = FileRead(argv[0], &elf.size);
@elf64_debug_print("Load file '%s', size = %d bytes\n", argv[0], elf.size);
if (!is_valid_elf(&elf)) {
PrintErr("File is not a valid ELF x86-64 executable.\n");
return;
}
process_elf_section_header_table(&elf);
process_elf_rela_dyn_entries(&elf);
process_elf_rela_plt_entries(&elf);
_start = elf.ehdr->e_entry;
elf_argc = argc;
elf_argv = argv;
}

317
System/FFI/LibC.HC Normal file
View file

@ -0,0 +1,317 @@
#define stdin 0
#define stdout 1
#define stderr 2
U0 bcmp()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
MemCmp(p0, p1, p2);
POP_SYSV_REGS
}
U0 calloc()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
CAlloc(p0 * p1, adam_task->code_heap);
POP_SYSV_REGS
}
U0 free()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
Free(p0);
POP_SYSV_REGS
}
I64 @isatty()
{
return 0;
}
U0 isatty()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
// Dbg;
@isatty;
POP_SYSV_REGS
}
I64 @fwrite(U8* ptr, I64 size, I64 nmemb, U64 stream)
{
U8* tmp;
switch (stream) {
case stdout:
case stderr:
tmp = CAlloc((size * nmemb) + 1, adam_task->code_heap);
MemCpy(tmp, ptr, (size * nmemb));
#ifdef QEMU_RUN_TESTS
QemuDebugMsg(tmp);
#endif
DocPutS(adam_task->put_doc, tmp);
Free(tmp);
// if (!MemCmp(tmp, "VERIFICATION FAILED", 19))
// Break;
break;
default:
break;
}
return size * nmemb;
}
U0 fwrite()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@fwrite(p0, p1, p2, p3);
POP_SYSV_REGS
}
U64 @getentropy(U8* buffer, U64 length)
{
I64 i;
for (i = 0; i < length; i++)
buffer[i] = RandU64;
return 0;
}
U0 getentropy()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@getentropy(p0, p1);
POP_SYSV_REGS
}
U0 htonl()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
EndianU32(p0);
POP_SYSV_REGS
}
U0 ntohl()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
EndianU32(p0);
POP_SYSV_REGS
}
U0 htons()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
EndianU16(p0);
POP_SYSV_REGS
}
U0 ntohs()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
EndianU16(p0);
POP_SYSV_REGS
}
U0 malloc()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
MAlloc(p0, adam_task->code_heap);
POP_SYSV_REGS
}
U0 memcmp()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
MemCmp(p0, p1, p2);
POP_SYSV_REGS
}
U0 memcpy()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
MemCpy(p0, p1, p2);
POP_SYSV_REGS
}
U8* @memmove(U8* dest, U8* src, I64 n)
{
I64 i;
U8* from = src;
U8* to = dest;
if (from == to || n == 0)
return dest;
if (to > from && to - from < n) {
/* to overlaps with from */
/* <from......> */
/* <to........> */
/* copy in reverse, to avoid overwriting from */
for (i = n - 1; i >= 0; i--)
to[i] = from[i];
return dest;
}
if (from > to && from - to < n) {
/* to overlaps with from */
/* <from......> */
/* <to........> */
/* copy forwards, to avoid overwriting from */
for (i = 0; i < n; i++)
to[i] = from[i];
return dest;
}
MemCpy(dest, src, n);
return dest;
}
U0 memmove()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@memmove(p0, p1, p2);
POP_SYSV_REGS
}
U0 memset()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
MemSet(p0, p1, p2);
POP_SYSV_REGS
}
U0 putc()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
PutChars(p0);
POP_SYSV_REGS
}
U0 rand()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
RandU64;
POP_SYSV_REGS
}
U8* @realloc(U8* ptr, I64 size)
{
U8* new;
if (!ptr) {
new = MAlloc(size, adam_task->code_heap);
} else {
new = MAlloc(size, adam_task->code_heap);
MemCpy(new, ptr, size);
Free(ptr);
}
return new;
}
U0 realloc()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@realloc(p0, p1);
POP_SYSV_REGS
}
// FIXME: It is non-obvious how to take a [u8] and convert it to a
// formatted string in Jakt, so we have to do this hack for
// now. Hopefully, this will change soon.
U0 sprintf()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
StrPrint(p0, p1, p2, p3, p4, p5);
POP_SYSV_REGS
}
I64 @strncmp(U8* s1, U8* s2, I32 n)
{
U64 u1, u2;
while (n-- > 0) {
u1 = *s1++;
u2 = *s2++;
u1 = u1 & 0xff;
u2 = u2 & 0xff;
if (u1 != u2)
return u1 - u2;
if (u1 == '\0')
return 0;
}
return 0;
}
U0 strncmp()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@strncmp(p0, p1, p2);
POP_SYSV_REGS
}
U0 strcmp()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
StrCmp(p0, p1);
POP_SYSV_REGS
}
U0 strlen()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
StrLen(p0);
POP_SYSV_REGS
}
I64 tos_nist_offset = 5020;
#define NIST_TIME_OFFSET (tos_nist_offset - local_time_offset / CDATE_FREQ)
public
I64 CDate2Unix(CDate dt)
{ // TempleOS datetime to Unix timestamp.
return ToI64((dt - Str2Date("1/1/1970")) / CDATE_FREQ + NIST_TIME_OFFSET);
}
I64 @time(I64* ptr)
{
no_warn ptr;
return CDate2Unix(Now);
}
U0 time()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@time(p0);
POP_SYSV_REGS
}
U0 toupper()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
ToUpper(p0);
POP_SYSV_REGS
}
U0 __assert_fail()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
"%s:%d: %s: %s\n", p1, p2, p3, p0;
Break;
POP_SYSV_REGS
}

35
System/FFI/New.HC Normal file
View file

@ -0,0 +1,35 @@
U0 _ZdlPv()
{
// operator delete(void*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
Free(p0);
POP_SYSV_REGS
}
U0 _ZdlPvm()
{
// operator delete(void*, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
Free(p0);
POP_SYSV_REGS
}
U0 _Znwm()
{
// operator new(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
MAlloc(p0, adam_task);
POP_SYSV_REGS
}
U0 _ZnwmRKSt9nothrow_t()
{
// operator new(unsigned long, std::nothrow_t const&)
PUSH_SYSV_REGS
GET_SYSV_ARGS
MAlloc(p0, adam_task);
POP_SYSV_REGS
}

288
System/Jakt/DC.HC Normal file
View file

@ -0,0 +1,288 @@
U0 _Z8dc_aliasm()
{
// dc_alias(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
DCAlias(p0);
POP_SYSV_REGS
}
U0 _Z7dc_blotmmmm()
{
// dc_blot(unsigned long, unsigned long, unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GrBlot(p0, p1, p2, p3);
POP_SYSV_REGS
}
U8* @dc_buffer(CDC* dc) { return dc->body; }
U0 _Z9dc_bufferm()
{
// dc_buffer(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_buffer(p0);
POP_SYSV_REGS
}
I64 @dc_color(CDC* dc) { return dc->color; }
U0 _Z8dc_colorm()
{
// dc_color(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_color(p0);
POP_SYSV_REGS
}
U0 @dc_copy(CDC* dest, I64 x, I64 y, CDC* src)
{
// If position is off-screen, return
if (x > dest->width - 1 || y > dest->height - 1)
return;
// If device context dimensions match, MemCpy and return
if (dest->width_internal == src->width_internal && dest->height == src->height) {
MemCpy(dest->body, src->body, dest->width_internal * dest->height);
return;
}
CDC* dc1 = DCAlias(dest);
CDC* dc2 = DCAlias(src);
I64 src_line = 0;
I64 src_row = 0;
I64 clip_y = 0;
// Handle horizontal clipping left
while (x < 0) {
dc2->x0++;
x++;
}
// Handle vertical clipping top
while (y < 0) {
dc2->body += src->width_internal;
dc2->y0++;
y++;
}
// default, clip line to copy as width-left off screen
src_line = src->width - dc2->x0;
if (-dc2->x0 + x + src->width >= dest->width) {
src_line -= ((-dc2->x0 + x + src->width) - dest->width);
}
dc2->body += dc2->x0;
clip_y = y;
while (src_row < (src->height - dc2->y0) && clip_y < dest->height) {
MemCpy(dc1->body + (y * dest->width) + x, dc2->body, src_line);
dc2->body += src->width_internal;
dc1->body += dest->width_internal;
clip_y++;
src_row++;
}
Free(dc2);
Free(dc1);
}
U0 _Z7dc_copymmmm()
{
// dc_copy(unsigned long, unsigned long, unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_copy(p0, p1, p2, p3);
POP_SYSV_REGS
}
U0 _Z10dc_destroym()
{
// dc_destroy(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
DCDel(p0);
POP_SYSV_REGS
}
U0 _Z14dc_draw_circlemlll()
{
// dc_draw_circle(unsigned long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GrCircle3(p0, p1, p2, 0, p3);
POP_SYSV_REGS
}
U0 _Z19dc_draw_filled_rectmllll()
{
// dc_draw_filled_rect(unsigned long, long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GrRect(p0, p1, p2, p3, p4);
POP_SYSV_REGS
}
U0 _Z12dc_draw_linemllll()
{
// dc_draw_line(unsigned long, long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GrLine3(p0, p1, p2, 0, p3, p4, 0);
POP_SYSV_REGS
}
U0 _Z13dc_draw_pixelmll()
{
// dc_draw_pixel(unsigned long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GrPlot(p0, p1, p2);
POP_SYSV_REGS
}
U0 _Z7dc_fillmm()
{
// dc_fill(unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
DCFill(p0, p1);
POP_SYSV_REGS
}
CDC* @dc_gr_dc() { return gr.dc; }
U0 _Z8dc_gr_dcv()
{
// dc_gr_dc()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_gr_dc();
POP_SYSV_REGS
}
I64 @dc_height(CDC* dc) { return dc->height; }
U0 _Z9dc_heightm()
{
// dc_height(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_height(p0);
POP_SYSV_REGS
}
U0 _Z17dc_load_from_filePKc()
{
// dc_load_from_file(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GRRead(p0);
POP_SYSV_REGS
}
U0 _Z6dc_newmm()
{
// dc_new(unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
DCNew(p0, p1);
POP_SYSV_REGS
}
U0 _Z11dc_pixel_atmll()
{
// dc_pixel_at(unsigned long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GrPeek(p0, p1, p2);
POP_SYSV_REGS
}
U0 _Z16dc_replace_colormmm()
{
// dc_replace_color(unsigned long, unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
DCColorChg(p0, p1, p2);
POP_SYSV_REGS
}
U0 _Z13dc_screenshotv()
{
// dc_screenshot()
PUSH_SYSV_REGS
GET_SYSV_ARGS
DCScrnCapture(1);
POP_SYSV_REGS
}
U0 _Z15dc_save_to_filePKcm()
{
// dc_save_to_file(char const*, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GRWrite(p0, p1);
POP_SYSV_REGS
}
U0 @dc_set_color(CDC* dc, I64 color) { dc->color = color; }
U0 _Z12dc_set_colorml()
{
// dc_set_color(unsigned long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_set_color(p0, p1);
POP_SYSV_REGS
}
U0 @dc_set_thickness(CDC* dc, I64 thickness) { dc->thick = thickness; }
U0 _Z16dc_set_thicknessml()
{
// dc_set_thickness(unsigned long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_set_thickness(p0, p1);
POP_SYSV_REGS
}
I64 @dc_thickness(CDC* dc) { return dc->thick; }
U0 _Z12dc_thicknessm()
{
// dc_thickness(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_thickness(p0);
POP_SYSV_REGS
}
I64 @dc_width(CDC* dc) { return dc->width; }
U0 _Z8dc_widthm()
{
// dc_width(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_width(p0);
POP_SYSV_REGS
}
I64 @dc_width_internal(CDC* dc) { return dc->width_internal; }
U0 _Z17dc_width_internalm()
{
// dc_width_internal(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@dc_width_internal(p0);
POP_SYSV_REGS
}

53
System/Jakt/IOPort.HC Normal file
View file

@ -0,0 +1,53 @@
U0 _Z14ioport_read_u8t()
{
// ioport_read_u8(unsigned short)
PUSH_SYSV_REGS
GET_SYSV_ARGS
InU8(p0);
POP_SYSV_REGS
}
U0 _Z15ioport_read_u16t()
{
// ioport_read_u16(unsigned short)
PUSH_SYSV_REGS
GET_SYSV_ARGS
InU16(p0);
POP_SYSV_REGS
}
U0 _Z15ioport_read_u32t()
{
// ioport_read_u32(unsigned short)
PUSH_SYSV_REGS
GET_SYSV_ARGS
InU32(p0);
POP_SYSV_REGS
}
U0 _Z15ioport_write_u8th()
{
// ioport_write_u8(unsigned short, unsigned char)
PUSH_SYSV_REGS
GET_SYSV_ARGS
OutU8(p0, p1);
POP_SYSV_REGS
}
U0 _Z16ioport_write_u16tt()
{
// ioport_write_u16(unsigned short, unsigned short)
PUSH_SYSV_REGS
GET_SYSV_ARGS
OutU16(p0, p1);
POP_SYSV_REGS
}
U0 _Z16ioport_write_u32tj()
{
// ioport_write_u32(unsigned short, unsigned int)
PUSH_SYSV_REGS
GET_SYSV_ARGS
OutU32(p0, p1);
POP_SYSV_REGS
}

72
System/Jakt/Input.HC Normal file
View file

@ -0,0 +1,72 @@
U0 _Z16input_get_stringPKc()
{
// input_get_string(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
GetStr(p0);
POP_SYSV_REGS
}
Bool @input_key_down(U8 scancode) { return Bt(kbd.down_bitmap, scancode); }
U0 _Z14input_key_downh()
{
// input_key_down(unsigned char)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@input_key_down(p0);
POP_SYSV_REGS
}
Bool @input_mouse_left() { return ms.lb; }
U0 _Z16input_mouse_leftv()
{
// input_mouse_left()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@input_mouse_left();
POP_SYSV_REGS
}
Bool @input_mouse_right() { return ms.rb; }
U0 _Z17input_mouse_rightv()
{
// input_mouse_right()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@input_mouse_right();
POP_SYSV_REGS
}
I64 @input_mouse_x() { return ms.pos.x; }
U0 _Z13input_mouse_xv()
{
// input_mouse_x()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@input_mouse_x();
POP_SYSV_REGS
}
I64 @input_mouse_y() { return ms.pos.y; }
U0 _Z13input_mouse_yv()
{
// input_mouse_y()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@input_mouse_y();
POP_SYSV_REGS
}
U0 _Z17input_press_a_keyv()
{
// input_press_a_key()
PUSH_SYSV_REGS
GET_SYSV_ARGS
PressAKey;
POP_SYSV_REGS
}

289
System/Jakt/OS.HC Normal file
View file

@ -0,0 +1,289 @@
U0 _Z8os_blinkPKc()
{
// os_blink(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
F64 frequency = Str2F64(p0);
Print("called os_blink(%.1f)\n", frequency);
Blink(frequency);
POP_SYSV_REGS
}
U64 @os_call(U8* function_name, U64 arg)
{
if (!function_name)
return NULL;
if (!StrLen(function_name))
return NULL;
CHash* h = HashFind(function_name, Fs->hash_table, Fs->hash_table->mask);
if (!h)
return NULL;
if (h->type & HTT_FUN == HTT_FUN) {
CallInd(h(CHashFun*)->exe_addr, arg);
} else {
return NULL;
}
}
U0 _Z7os_callmm()
{
// os_call(unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@os_call(p0, p1);
POP_SYSV_REGS
}
U0 _Z16os_device_callocj()
{
// os_device_calloc(unsigned int)
PUSH_SYSV_REGS
GET_SYSV_ARGS
CAllocAligned(p0, 4096, adam_task->code_heap);
POP_SYSV_REGS
}
U0 _Z7os_exitv()
{
// os_exit()
PUSH_SYSV_REGS
GET_SYSV_ARGS
UserTaskCont;
POP_SYSV_REGS
}
U8* @os_file_picker(U8* path, U8* glob)
{
U8* full_path = CAlloc(StrLen(path) + StrLen(glob) + 4, adam_task);
CatPrint(full_path, "%s/%s", path, glob);
CDirEntry* de = FilesFind(full_path);
Free(full_path);
CDirEntry* tmpde;
U8* file_list = NULL;
U8* selected_file = NULL;
I64 list_pos = 0;
I64 list_size = 0;
tmpde = de;
while (tmpde) {
list_size += StrLen(tmpde->name) + 2;
tmpde = tmpde->next;
}
file_list = CAlloc(list_size, adam_task);
tmpde = de;
while (tmpde) {
StrCpy(file_list + list_pos, tmpde->name);
list_pos += StrLen(tmpde->name) + 1;
tmpde = tmpde->next;
}
I64 list_index = Adam("PopUpPickLst(0x%08x);\n", file_list);
Free(file_list);
list_pos = 0;
if (list_index < 0) {
DirTreeDel(de);
return StrNew("", adam_task);
}
tmpde = de;
while (tmpde) {
if (list_index == list_pos) {
selected_file = CAlloc(StrLen(path) + StrLen(tmpde->name) + 4, adam_task);
CatPrint(selected_file, "%s/%s", path, tmpde->name);
break;
}
StrCpy(file_list + list_pos, tmpde->name);
list_pos++;
tmpde = tmpde->next;
}
DirTreeDel(de);
return selected_file;
}
U0 _Z14os_file_pickerPKcS0_()
{
// os_file_picker(char const*, char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@os_file_picker(p0, p1);
POP_SYSV_REGS
}
U8* @os_files_list(U8* path)
{
U8* full_path = CAlloc(StrLen(path) + 4, adam_task);
CatPrint(full_path, "%s", path);
CDirEntry* de = FilesFind(full_path);
Free(full_path);
CDateStruct ds;
CDirEntry* tmpde;
U8* file_list = NULL;
I64 list_size = 0;
tmpde = de;
while (tmpde) {
list_size += StrLen(tmpde->name) + 48; // Should be enough for filename, date,
// filesize + semicolon separators
tmpde = tmpde->next;
}
if (!list_size)
return NULL;
file_list = CAlloc(list_size, adam_task);
tmpde = de;
I64 counter = 0;
while (tmpde) {
if (counter > 0) {
StrCpy(file_list + StrLen(file_list), "|");
}
StrCpy(file_list + StrLen(file_list), tmpde->name);
if (tmpde->attr & RS_ATTR_DIR)
StrCpy(file_list + StrLen(file_list), "/");
StrCpy(file_list + StrLen(file_list), ";");
Date2Struct(&ds, tmpde->datetime);
StrPrint(file_list + StrLen(file_list), "%04d-%02d-%02d %02d:%02d", ds.year,
ds.mon, ds.day_of_mon, ds.hour, ds.min);
StrCpy(file_list + StrLen(file_list), ";");
StrPrint(file_list + StrLen(file_list), "%d", tmpde->size);
tmpde = tmpde->next;
counter++;
}
DirTreeDel(de);
return file_list;
}
U0 _Z14os_path_existsPKc()
{
// os_path_exists(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
FileFind(p0);
POP_SYSV_REGS
}
U0 _Z13os_files_listPKc()
{
// os_files_list(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@os_files_list(p0);
POP_SYSV_REGS
}
Bool @os_is_vm()
{
CRAXRBCRCXRDX res;
CPUId(0x40000000, &res);
if (res.rbx == 0x4B4D564B)
return TRUE;
return FALSE;
}
U0 _Z8os_is_vmv()
{
PUSH_SYSV_REGS
GET_SYSV_ARGS
@os_is_vm;
POP_SYSV_REGS
}
U0 @os_pc_speaker(F64 frequency)
{
I64 period;
if (!frequency)
OutU8(0x61, InU8(0x61) & ~3);
else {
period = ClampI64(SYS_TIMER_FREQ / frequency, 1, U16_MAX);
OutU8(0x43, 0xB6);
OutU8(0x42, period);
OutU8(0x42, period.u8[1]);
OutU8(0x61, 3 | InU8(0x61));
}
}
U0 _Z13os_pc_speakerPKc()
{
// os_pc_speaker(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
F64 frequency = Str2F64(p0);
@os_pc_speaker(frequency);
POP_SYSV_REGS
}
U0 _Z9os_randomv()
{
// os_random()
PUSH_SYSV_REGS
GET_SYSV_ARGS
RandU64;
POP_SYSV_REGS
}
U0 _Z19os_read_entire_filePKcPl()
{
// os_read_entire_file(char const*, long*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
FileRead(p0, p1);
POP_SYSV_REGS
}
U0 @os_screenshot()
{
CDC* dc = DCScrnCapture(, adam_task);
Image.Write("B:/screenshot.png", dc);
DCDel(dc);
}
U0 _Z13os_screenshotv()
{
// os_screenshot()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@os_screenshot;
POP_SYSV_REGS
}
U8* @os_to_uppercase(U8* instr)
{
if (!instr)
return NULL;
if (!StrLen(instr))
return NULL;
U8* outstr = CAlloc(StrLen(instr) + 1, adam_task);
I64 i;
for (i = 0; i < StrLen(instr); i++)
outstr[i] = ToUpper(instr[i]);
return outstr;
}
U0 _Z15os_to_uppercasePKc()
{
// os_to_uppercase(char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@os_to_uppercase(p0);
POP_SYSV_REGS
}
U0 _Z20os_write_entire_filePKcPhl()
{
// os_write_entire_file(char const*, unsigned char*, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
FileWrite(p0, p1, p2);
POP_SYSV_REGS
}

62
System/Jakt/PCI.HC Normal file
View file

@ -0,0 +1,62 @@
U0 _Z8pci_findl()
{
// pci_find(long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIClassFind(p0, 0);
POP_SYSV_REGS
}
U0 _Z11pci_read_u8llll()
{
// pci_read_u8(long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIReadU8(p0, p1, p2, p3);
POP_SYSV_REGS
}
U0 _Z12pci_read_u16llll()
{
// pci_read_u16(long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIReadU16(p0, p1, p2, p3);
POP_SYSV_REGS
}
U0 _Z12pci_read_u32llll()
{
// pci_read_u32(long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIReadU32(p0, p1, p2, p3);
POP_SYSV_REGS
}
U0 _Z12pci_write_u8llllh()
{
// pci_write_u8(long, long, long, long, unsigned char)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIWriteU8(p0, p1, p2, p3, p4);
POP_SYSV_REGS
}
U0 _Z13pci_write_u16llllt()
{
// pci_write_u16(long, long, long, long, unsigned short)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIWriteU16(p0, p1, p2, p3, p4);
POP_SYSV_REGS
}
U0 _Z13pci_write_u32llllj()
{
// pci_write_u32(long, long, long, long, unsigned int)
PUSH_SYSV_REGS
GET_SYSV_ARGS
PCIWriteU32(p0, p1, p2, p3, p4);
POP_SYSV_REGS
}

37
System/Jakt/Time.HC Normal file
View file

@ -0,0 +1,37 @@
U0 _Z9time_busyl()
{
// time_busy(long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
Busy(p0);
POP_SYSV_REGS
}
I64 @time_jiffies() { return cnts.jiffies; }
U0 _Z12time_jiffiesv()
{
// time_jiffies()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@time_jiffies;
POP_SYSV_REGS
}
U0 _Z8time_nowv()
{
// time_now()
PUSH_SYSV_REGS
GET_SYSV_ARGS
Now;
POP_SYSV_REGS
}
U0 _Z10time_sleepl()
{
// time_sleep(long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
Sleep(p0);
POP_SYSV_REGS
}

94
System/Jakt/Window.HC Normal file
View file

@ -0,0 +1,94 @@
U0 @window_draw_it(CTask* task, CDC* dc)
{
if (task->user_data)
@dc_copy(dc, task->pix_left, task->pix_top, task->user_data);
}
CTask* @window_user()
{
CTask* task = Spawn(&UserCmdLine, , , 0);
TaskWait(task);
XTalk(task,
"while (1) { StrCpy(Fs->task_title, Fs->task_name); Sleep(1); };\n");
return task;
}
CTask* @window_create()
{
CTask* task = @window_user;
task->draw_it = &@window_draw_it;
return task;
}
U0 _Z13window_createv()
{
// window_create()
PUSH_SYSV_REGS
GET_SYSV_ARGS
@window_create();
POP_SYSV_REGS
}
U0 _Z14window_destroym()
{
// window_destroy(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
Kill(p0);
POP_SYSV_REGS
}
Bool @window_is_focused(CTask* task) { return task == sys_focus_task; }
U0 _Z17window_is_focusedm()
{
// window_is_focused(unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@window_is_focused(p0);
POP_SYSV_REGS
}
U0 @window_set_coordinates(CTask* task, I64 top, I64 left, I64 bottom,
I64 right)
{
task->win_top = top;
task->win_left = left;
task->win_bottom = bottom;
task->win_right = right;
}
U0 _Z22window_set_coordinatesmllll()
{
// window_set_coordinates(unsigned long, long, long, long, long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@window_set_coordinates(p0, p1, p2, p3, p4);
POP_SYSV_REGS
}
U0 @window_set_context(CTask* task, CDC* dc) { task->user_data = dc; }
U0 _Z18window_set_contextmm()
{
// window_set_context(unsigned long, unsigned long)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@window_set_context(p0, p1);
POP_SYSV_REGS
}
U0 @window_set_title(CTask* task, U8* title)
{
StrCpy(task->task_name, title);
StrCpy(task->task_title, title);
}
U0 _Z16window_set_titlemPKc()
{
// window_set_title(unsigned long, char const*)
PUSH_SYSV_REGS
GET_SYSV_ARGS
@window_set_title(p0, p1);
POP_SYSV_REGS
}

View file

@ -0,0 +1,88 @@
U8* @base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
U8* @base64_decode(U8* input, I64* output_length)
{
I64 input_length = StrLen(input);
if (input_length % 4 != 0) {
return NULL; // Invalid Base64 input length
}
// Calculate the expected output length
*output_length = (3 * input_length) / 4;
if (input[input_length - 1] == '=') {
(*output_length)--;
}
if (input[input_length - 2] == '=') {
(*output_length)--;
}
// Allocate memory for the decoded data
U8* decoded_data = CAlloc(*output_length, adam_task);
if (decoded_data == NULL) {
return NULL; // Memory allocation failed
}
// Initialize variables for decoding process
I32 i, j = 0;
U32 sextet_bits = 0;
I64 sextet_count = 0;
U32 base64_value;
U8* char_pointer;
U8 input_find_buf[2];
input_find_buf[1] = NULL;
// Loop through the Base64 input and decode it
for (i = 0; i < input_length; i++) {
// Convert Base64 character to a 6-bit value
base64_value = 0;
if (input[i] == '=') {
base64_value = 0;
} else {
input_find_buf[0] = input[i];
char_pointer = StrFirstOcc(@base64_chars, input_find_buf);
if (char_pointer == NULL) {
Free(decoded_data);
return NULL; // Invalid Base64 character
}
base64_value = char_pointer - @base64_chars;
}
// Combine 6-bit values into a 24-bit sextet
sextet_bits = (sextet_bits << 6) | base64_value;
sextet_count++;
// When a sextet is complete, decode it into three bytes
if (sextet_count == 4) {
decoded_data[j++] = (sextet_bits >> 16) & 0xFF;
decoded_data[j++] = (sextet_bits >> 8) & 0xFF;
decoded_data[j++] = sextet_bits & 0xFF;
sextet_bits = 0;
sextet_count = 0;
}
}
return decoded_data;
}
U8* @base64_encode(U8* input, I64 input_length)
{
I64 i;
U8 buf[3];
I64 c = 0;
U8* output = CAlloc(input_length * 2, adam_task);
for (i = 0; i < input_length; i += 3) {
buf[0] = input[i];
buf[1] = @t((i + 1 < input_length), input[i + 1], 0);
buf[2] = @t((i + 2 < input_length), input[i + 2], 0);
output[c++] = @base64_chars[(buf[0] & 0xfc) >> 2];
output[c++] = @base64_chars[((buf[0] & 0x03) << 4) + ((buf[1] & 0xf0) >> 4)];
output[c++] = @t((i + 1 < input_length), @base64_chars[((buf[1] & 0x0f) << 2) + ((buf[2] & 0xc0) >> 6)], '=');
output[c++] = @t((i + 2 < input_length), @base64_chars[buf[2] & 0x3f], '=');
}
output[c] = '\0';
return output;
}

628
System/Libraries/Http.HC Normal file
View file

@ -0,0 +1,628 @@
#define HTTP_TMP_DIRECTORY "B:/Tmp"
#define HTTP_CACHE_DIRECTORY "B:/Tmp/Cache"
#define HTTP_FETCH_BUFFER_SIZE 1024 << 15
#define SEDECIM_USER_AGENT_STRING "Mozilla/5.0 (compatible; Sedecim/1.0; TempleOS) (KHTML, like Gecko)"
class @http_buffer
{
I64 length;
U8* data;
};
class @http_status
{
U8* protocol;
I64 code;
U8* text;
};
class @http_response
{
I64 state;
TlsSocket* s;
@http_status status;
JsonObject* headers;
@http_buffer body;
Bool headers_parsed;
};
class @http_url
{
U8* scheme;
U8* host;
I64 port;
U8* path;
U8* query;
U8* fragment;
};
class @http_request
{
@http_url* url;
U8* buf;
U8* data;
I64 type;
JsonObject* headers;
@http_response* response;
};
#define HttpResponse @http_response
#define HttpUrl @http_url
#define HTTP_FETCH_BUFFER_SIZE 16777216
#define HTTP_PARSE_SCHEME 0
#define HTTP_PARSE_SCHEME_FS 1
#define HTTP_PARSE_HOST 2
#define HTTP_PARSE_PORT 3
#define HTTP_PARSE_PATH 4
#define HTTP_PARSE_QUERY 5
#define HTTP_PARSE_FRAGMENT 6
#define HTTP_MIN_REQUEST_BUFFER_SIZE 16384
#define HTTP_PARSE_URL_FIFO_SIZE 1024
#define HTTP_REQ_GET 0
#define HTTP_REQ_HEAD 1
#define HTTP_REQ_POST 2
#define HTTP_REQ_PUT 3
#define HTTP_STATE_UNSENT 0
#define HTTP_STATE_OPENED 1
#define HTTP_STATE_HEADERS_RECEIVED 2
#define HTTP_STATE_LOADING 3
#define HTTP_STATE_DONE 4
U8* @http_string_from_fifo(CFifoU8* f)
{
U8 ch;
I64 i = 0;
U8* str = CAlloc(FifoU8Cnt(f) + 1, adam_task);
while (FifoU8Cnt(f)) {
FifoU8Rem(f, &ch);
str[i] = ch;
i++;
}
FifoU8Flush(f);
return str;
}
U0 @http_free_url(@http_url* url)
{
if (!url)
return;
if (url->scheme)
Free(url->scheme);
if (url->host)
Free(url->host);
if (url->path)
Free(url->path);
if (url->query)
Free(url->query);
if (url->fragment)
Free(url->fragment);
Free(url);
}
U0 @http_free_response(@http_response* resp)
{
if (!resp)
return;
// FIXME: Free response headers JSON object
Free(resp);
}
@http_url* @http_parse_url(U8* str)
{
if (!str)
return NULL;
U8* buf = NULL;
U8 hex[3];
I64 i = 0;
I64 state = HTTP_PARSE_SCHEME;
CFifoU8* consume_fifo = FifoU8New(HTTP_PARSE_URL_FIFO_SIZE, adam_task);
@http_url* url = CAlloc(sizeof(@http_url), adam_task);
while (1) {
switch (str[i]) {
case 0:
switch (state) {
case HTTP_PARSE_HOST:
url->host = @http_string_from_fifo(consume_fifo);
url->path = StrNew("/", adam_task);
goto done_parsing_url;
break;
case HTTP_PARSE_PORT:
buf = @http_string_from_fifo(consume_fifo);
url->port = Str2I64(buf);
Free(buf);
url->path = StrNew("/", adam_task);
goto done_parsing_url;
break;
case HTTP_PARSE_PATH:
url->path = @http_string_from_fifo(consume_fifo);
goto done_parsing_url;
break;
case HTTP_PARSE_QUERY:
url->query = @http_string_from_fifo(consume_fifo);
goto done_parsing_url;
break;
case HTTP_PARSE_FRAGMENT:
url->fragment = @http_string_from_fifo(consume_fifo);
goto done_parsing_url;
break;
default:
goto done_parsing_url;
break;
}
break;
case '#':
switch (state) {
case HTTP_PARSE_PATH:
url->path = @http_string_from_fifo(consume_fifo);
FifoU8Ins(consume_fifo, str[i]);
state = HTTP_PARSE_FRAGMENT;
break;
case HTTP_PARSE_QUERY:
url->query = @http_string_from_fifo(consume_fifo);
FifoU8Ins(consume_fifo, str[i]);
state = HTTP_PARSE_FRAGMENT;
break;
}
break;
case '?':
switch (state) {
case HTTP_PARSE_PATH:
url->path = @http_string_from_fifo(consume_fifo);
FifoU8Ins(consume_fifo, str[i]);
state = HTTP_PARSE_QUERY;
break;
}
break;
case '/':
switch (state) {
case HTTP_PARSE_SCHEME:
state = HTTP_PARSE_SCHEME_FS;
goto keep_consuming_url_chars;
break;
case HTTP_PARSE_SCHEME_FS:
FifoU8Ins(consume_fifo, str[i]);
url->scheme = @http_string_from_fifo(consume_fifo);
if (!StrCmp(url->scheme, "http://"))
url->port = 80;
if (!StrCmp(url->scheme, "https://"))
url->port = 443;
state = HTTP_PARSE_HOST;
break;
case HTTP_PARSE_HOST:
url->host = @http_string_from_fifo(consume_fifo);
FifoU8Ins(consume_fifo, str[i]);
state = HTTP_PARSE_PATH;
break;
case HTTP_PARSE_PORT:
buf = @http_string_from_fifo(consume_fifo);
url->port = Str2I64(buf);
Free(buf);
FifoU8Ins(consume_fifo, str[i]);
state = HTTP_PARSE_PATH;
break;
case HTTP_PARSE_PATH:
goto keep_consuming_url_chars;
break;
}
break;
case ':':
switch (state) {
case HTTP_PARSE_SCHEME:
case HTTP_PARSE_PATH:
case HTTP_PARSE_QUERY:
case HTTP_PARSE_FRAGMENT:
goto keep_consuming_url_chars;
break;
case HTTP_PARSE_HOST:
url->host = @http_string_from_fifo(consume_fifo);
state = HTTP_PARSE_PORT;
break;
}
break;
default:
keep_consuming_url_chars:
switch (state) {
case HTTP_PARSE_PATH:
case HTTP_PARSE_QUERY:
switch (str[i]) {
case '0' ... '9':
case 'A' ... 'Z':
case 'a' ... 'z':
case '?':
case '&':
case '/':
case '=':
// !'()*-._~
case '!':
case '\'':
case '(':
case ')':
case '*':
case '-':
case '.':
case '_':
case '~':
case '%':
FifoU8Ins(consume_fifo, str[i]);
break;
default:
FifoU8Ins(consume_fifo, '%');
StrPrint(hex, "%02X", str[i]);
FifoU8Ins(consume_fifo, hex[0]);
FifoU8Ins(consume_fifo, hex[1]);
break;
}
break;
default:
FifoU8Ins(consume_fifo, str[i]);
break;
}
break;
}
i++;
}
done_parsing_url:
FifoU8Flush(consume_fifo);
FifoU8Del(consume_fifo);
return url;
}
U0 @http_parse_response_headers(@http_response* resp, U8* buffer, I64 length)
{
if (!resp || !buffer || !length)
return;
U64 response_data_ptr = StrFind("\r\n\r\n", buffer);
if (!response_data_ptr)
return;
resp->body.data = response_data_ptr + 4;
resp->body.data[-4] = NULL;
JsonObject* headers = Json.CreateObject();
U8** lines = NULL;
I64 lines_count = 0;
I64 i;
I64 j;
U8* key_ptr = NULL;
U8* value_ptr = NULL;
lines = String.Split(buffer, '\n', &lines_count);
U8* resp_protocol = lines[0];
U8* resp_status_code = StrFind(" ", resp_protocol) + 1;
U8* resp_text = StrFind(" ", resp_status_code + 1);
(*StrFind(" ", resp_protocol)) = NULL;
(*StrFind(" ", resp_status_code)) = NULL;
resp->status.protocol = StrNew(resp_protocol, adam_task);
resp->status.code = Str2I64(resp_status_code);
resp->status.text = StrNew(resp_text, adam_task);
for (i = 1; i < lines_count; i++) {
for (j = 0; j < StrLen(lines[i]); j++) {
if (lines[i][j] == ':' && lines[i][j + 1] == ' ') {
lines[i][j] = NULL;
key_ptr = lines[i];
value_ptr = lines[i] + j + 2;
(*StrFind("\r", value_ptr)) = NULL;
Json.Set(headers, key_ptr, StrNew(value_ptr, adam_task), JSON_STRING);
goto @http_next_header_line;
}
}
@http_next_header_line:
}
resp->headers = headers;
resp->headers_parsed = TRUE;
}
Bool @http_detect_response_headers(U8* buf, I64 len)
{
if (len < 4)
return FALSE;
I64 i;
for (i = 0; i < len - 4; i++) {
if (!MemCmp(buf + i, "\r\n\r\n", 4))
return TRUE;
}
return FALSE;
}
I64 @http_req(@http_request* req)
{
if (!req)
return NULL;
if (!req->url || !req->buf || !req->response)
return NULL;
if (!req->url->scheme || !req->url->host || !req->url->path)
return NULL;
if (req->type == HTTP_REQ_POST && !req->data)
return NULL;
if (req->type == HTTP_REQ_PUT && !req->data)
return NULL;
@http_response* resp = req->response;
U8* buf = NULL;
U8* headers_buf = "";
I64 cnt = 1;
I64 len = NULL;
buf = CAlloc(HTTP_MIN_REQUEST_BUFFER_SIZE, adam_task);
if (req->headers) {
headers_buf = CAlloc(HTTP_MIN_REQUEST_BUFFER_SIZE, adam_task);
JsonKey* key = req->headers->keys;
while (key) {
StrPrint(headers_buf + StrLen(headers_buf), "%s: %s\r\n", key->name, key->value);
key = key->next;
}
}
switch (req->type) {
case HTTP_REQ_GET:
StrPrint(buf,
"GET %s%s HTTP/1.0\r\n"
"Host: %s\r\n"
"%s"
"User-Agent: " SEDECIM_USER_AGENT_STRING
"\r\n\r\n",
req->url->path, req->url->query, req->url->host, headers_buf);
break;
case HTTP_REQ_HEAD:
StrPrint(buf,
"HEAD %s%s HTTP/1.0\r\n"
"Host: %s\r\n"
"%s"
"User-Agent: " SEDECIM_USER_AGENT_STRING
"\r\n\r\n",
req->url->path, req->url->query, req->url->host, headers_buf);
break;
case HTTP_REQ_POST:
StrPrint(buf,
"POST %s%s HTTP/1.0\r\n"
"Host: %s\r\n"
"%s"
"User-Agent: " SEDECIM_USER_AGENT_STRING
"\r\n"
"Content-Length: %d\r\n\r\n",
req->url->path, req->url->query, req->url->host, headers_buf,
StrLen(req->data));
StrPrint(buf + StrLen(buf), req->data);
break;
case HTTP_REQ_PUT:
StrPrint(buf,
"PUT %s%s HTTP/1.0\r\n"
"Host: %s\r\n"
"%s"
"User-Agent: " SEDECIM_USER_AGENT_STRING
"\r\n"
"Content-Length: %d\r\n\r\n",
req->url->path, req->url->query, req->url->host, headers_buf,
StrLen(req->data));
StrPrint(buf + StrLen(buf), req->data);
break;
}
TlsSocket* s = NULL;
resp->s = NULL;
if (!StrCmp(req->url->scheme, "http://")) {
s = @tcp_socket_create(req->url->host, req->url->port);
resp->s = s;
while (s->state != TCP_SOCKET_STATE_ESTABLISHED)
Sleep(1);
}
if (!StrCmp(req->url->scheme, "https://")) {
s = @tls_socket_create(req->url->host, req->url->port);
resp->s = s;
while (!@tls_established(s->ctx))
Sleep(1);
}
resp->state = HTTP_STATE_OPENED;
s->send(buf, StrLen(buf));
while (cnt || s->state != TCP_SOCKET_STATE_CLOSED) {
cnt = s->receive(req->buf + len, 1024);
len += cnt;
switch (resp->state) {
case HTTP_STATE_LOADING:
resp->body.length += cnt;
break;
case HTTP_STATE_HEADERS_RECEIVED:
resp->body.length = (req->buf + len) - resp->body.data;
resp->state = HTTP_STATE_LOADING;
break;
case HTTP_STATE_OPENED:
if (@http_detect_response_headers(req->buf, len)) {
@http_parse_response_headers(resp, req->buf, len);
resp->state = HTTP_STATE_HEADERS_RECEIVED;
}
break;
}
Sleep(1);
}
if (!resp->headers_parsed)
@http_parse_response_headers(resp, req->buf, len);
resp->state = HTTP_STATE_DONE;
req->buf[len] = NULL;
Free(buf);
if (StrLen(headers_buf) > 0) {
Free(headers_buf);
}
s->close();
resp->s = NULL;
Free(req);
return len;
}
Bool @http_scheme_is_https(@http_url* url)
{
if (!url || !url->scheme)
return FALSE;
return !MemCmp(url->scheme, "https", 5);
}
@http_response* @http_get(@http_url* url, U8* buf, U64 return_req = NULL, JsonObject* headers = NULL)
{
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
if (return_req)
MemCpy(return_req, &req, sizeof(U64));
req->url = url;
req->buf = buf;
req->type = HTTP_REQ_GET;
req->headers = headers;
req->response = resp;
Spawn(&@http_req, req, "HTTPGetRequest");
return resp;
}
@http_response* @http_head(@http_url* url, U8* buf, JsonObject* headers = NULL)
{
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
req->url = url;
req->buf = buf;
req->type = HTTP_REQ_HEAD;
req->headers = headers;
req->response = resp;
Spawn(&@http_req, req, "HTTPHeadRequest");
return resp;
}
@http_response* @http_post(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL)
{
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
req->url = url;
req->buf = buf;
req->type = HTTP_REQ_POST;
req->headers = headers;
req->data = data;
req->response = resp;
Spawn(&@http_req, req, "HTTPPostRequest");
return resp;
}
@http_response* @http_put(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL)
{
@http_response* resp = CAlloc(sizeof(@http_response), adam_task);
@http_request* req = CAlloc(sizeof(@http_request), adam_task);
req->url = url;
req->buf = buf;
req->type = HTTP_REQ_PUT;
req->headers = headers;
req->data = data;
req->response = resp;
Spawn(&@http_req, req, "HTTPPutRequest");
return resp;
}
class @http
{
@http_response* (*Get)(@http_url* url, U8* buf, U64* req = NULL, JsonObject* headers = NULL);
@http_response* (*Head)(@http_url* url, U8* buf, JsonObject* headers = NULL);
@http_response* (*Post)(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL);
@http_response* (*Put)(@http_url* url, U8* buf, U8* data, JsonObject* headers = NULL);
};
@http Http;
Http.Get = &@http_get;
Http.Head = &@http_head;
Http.Post = &@http_post;
Http.Put = &@http_put;
Bool @http_is_resource_cached(U8* src, U8* cache_directory = HTTP_CACHE_DIRECTORY)
{
U8 buf[512];
U8* src_md5 = md5_string(src, StrLen(src));
StrCpy(buf, cache_directory);
StrPrint(buf + StrLen(buf), "/%s", src_md5);
Free(src_md5);
return FileFind(buf);
}
U0 @http_cache_resource(U8* src, U8* data, I64 size, U8* cache_directory = HTTP_CACHE_DIRECTORY)
{
U8 buf[512];
U8* src_md5 = md5_string(src, StrLen(src));
StrCpy(buf, cache_directory);
StrPrint(buf + StrLen(buf), "/%s", src_md5);
Free(src_md5);
FileWrite(buf, data, size);
}
U8* @http_get_cached_resource_filename(U8* src, U8* cache_directory = HTTP_CACHE_DIRECTORY)
{
U8* buf = CAlloc(512, adam_task);
U8* src_md5 = md5_string(src, StrLen(src));
StrCpy(buf, cache_directory);
StrPrint(buf + StrLen(buf), "/%s", src_md5);
Free(src_md5);
return buf;
}
U0 @http_init_tmp_and_cache_directories()
{
if (!FileFind(HTTP_TMP_DIRECTORY))
DirMk(HTTP_TMP_DIRECTORY);
if (!FileFind(HTTP_CACHE_DIRECTORY))
DirMk(HTTP_CACHE_DIRECTORY);
}
@http_response* fetch(U8* url_string, U8* fetch_buffer)
{
if (!url_string || !fetch_buffer)
return NULL;
HttpUrl* url = @http_parse_url(url_string);
if (!url)
return NULL;
@http_response* resp = Http.Get(url, fetch_buffer);
while (resp->state != HTTP_STATE_DONE)
Sleep(1);
return resp;
}
I64 curl(U8* url_string)
{
if (!url_string)
return 0;
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
@http_response* resp = fetch(url_string, fetch_buffer);
if (!resp)
return 0;
if (resp->body.length) {
U8* buf = resp->body.data;
while (*buf) {
if (*buf == '\d')
"\d\d";
else
"%c", *buf;
++buf;
}
"\n";
}
Free(fetch_buffer);
return resp->body.length;
}
I64 download(U8* path, U8* url_string)
{
if (!path || !url_string)
return 0;
U8* fetch_buffer = CAlloc(HTTP_FETCH_BUFFER_SIZE, adam_task);
@http_response* resp = fetch(url_string, fetch_buffer);
if (!resp)
return 0;
if (resp->body.length) {
FileWrite(path, resp->body.data, resp->body.length);
}
Free(fetch_buffer);
return resp->body.length;
}
@http_init_tmp_and_cache_directories;

1354
System/Libraries/Json.HC Normal file

File diff suppressed because it is too large Load diff

44
System/Libraries/Rsa.HC Normal file
View file

@ -0,0 +1,44 @@
Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions
I64 @rsa_import(U8* der_bytes, I64 der_len, U64 key)
{
U64 reg RDI rdi = der_bytes;
U64 reg RSI rsi = der_len;
U64 reg RDX rdx = key;
no_warn rdi, rsi, rdx;
asm {
MOV RAX, RSA_IMPORT
CALL RAX
}
}
I64 @rsa_create_signature(U8* sig, I64* siglen, U8* hash, I64 hashlen, U64 key)
{
U64 reg RDI rdi = sig;
U64 reg RSI rsi = siglen;
U64 reg RDX rdx = hash;
U64 reg RCX rcx = hashlen;
U64 reg R8 r8 = key;
no_warn rdi, rsi, rdx, rcx, r8;
asm {
MOV RAX, RSA_CREATE_SIGNATURE
CALL RAX
}
}
I64 @rsa_verify_signature(U8* sig, I64 siglen, U8* hash, I64 hashlen, I32* stat, U64 key)
{
U64 reg RDI rdi = sig;
U64 reg RSI rsi = siglen;
U64 reg RDX rdx = hash;
U64 reg RCX rcx = hashlen;
U64 reg R8 r8 = stat;
U64 reg R9 r9 = key;
no_warn rdi, rsi, rdx, rcx, r8, r9;
asm {
MOV RAX, RSA_VERIFY_SIGNATURE
CALL RAX
}
}
Silent(0);

233
System/Libraries/Sha256.HC Normal file
View file

@ -0,0 +1,233 @@
#define CHUNK_SIZE 64
#define TOTAL_LEN_LEN 8
/*
* ABOUT bool: this file does not use bool in order to be as pre-C99 compatible
* as possible.
*/
/*
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are
* reproduced here. When useful for clarification, portions of the pseudo-code
* are reproduced here too.
*/
/*
* Initialize array of round constants:
* (first 32 bits of the fractional parts of the cube roots of the first 64
* primes 2..311):
*/
U32 k[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
U32 _h[8] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
class buffer_state {
U8* p;
I32 len;
I32 total_len;
I32 single_one_delivered; /* bool */
I32 total_len_delivered; /* bool */
};
U32 right_rot(U32 value, U32 count)
{
/*
* Defined behaviour in standard C for all count where 0 < count < 32,
* which is what we need here.
*/
return value >> count | value << (32 - count);
}
U0 init_buf_state(buffer_state* state, U8* input, I32 len)
{
state->p = input;
state->len = len;
state->total_len = len;
state->single_one_delivered = 0;
state->total_len_delivered = 0;
}
/* Return value: bool */
I32 calc_chunk(U8* chunk, buffer_state* state)
{
I32 space_in_chunk;
if (state->total_len_delivered) {
return 0;
}
if (state->len >= CHUNK_SIZE) {
MemCpy(chunk, state->p, CHUNK_SIZE);
state->p += CHUNK_SIZE;
state->len -= CHUNK_SIZE;
return 1;
}
MemCpy(chunk, state->p, state->len);
chunk += state->len;
space_in_chunk = CHUNK_SIZE - state->len;
state->p += state->len;
state->len = 0;
/* If we are here, space_in_chunk is one at minimum. */
if (!state->single_one_delivered) {
*chunk++ = 0x80;
space_in_chunk -= 1;
state->single_one_delivered = 1;
}
/*
* Now:
* - either there is enough space left for the total length, and we can
* conclude,
* - or there is too little space left, and we have to pad the rest of this
* chunk with zeroes. In the latter case, we will conclude at the next
* invokation of this function.
*/
if (space_in_chunk >= TOTAL_LEN_LEN) {
I32 left = space_in_chunk - TOTAL_LEN_LEN;
I32 len = state->total_len;
I32 i;
MemSet(chunk, 0x00, left);
chunk += left;
/* Storing of len * 8 as a big endian 64-bit without overflow. */
chunk[7] = (len << 3);
len >>= 5;
for (i = 6; i >= 0; i--) {
chunk[i] = len;
len >>= 8;
}
state->total_len_delivered = 1;
} else {
MemSet(chunk, 0x00, space_in_chunk);
}
return 1;
}
/*
* Limitations:
* - Since input is a pointer in RAM, the data to hash should be in RAM, which
* could be a problem for large data sizes.
* - SHA algorithms theoretically operate on bit strings. However, this
* implementation has no support for bit string lengths that are not multiples
* of eight, and it really operates on arrays of bytes. In particular, the len
* parameter is a number of bytes.
*/
U0 calc_sha_256(U8* hash, U8* input, I32 len)
{
/*
* Note 1: All integers (expect indexes) are 32-bit U32 integers and addition
* is calculated modulo 2^32. Note 2: For each round, there is one round
* constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
* Note 3: The compression function uses 8 working variables, a through h
* Note 4: Big-endian convention is used when expressing the constants in this
* pseudocode, and when parsing message block data from bytes to words, for
* example, the first word of the input message "abc" after padding is
* 0x61626380
*/
/*
* Initialize hash values:
* (first 32 bits of the fractional parts of the square roots of the first 8
* primes 2..19):
*/
U32 h[8];
U32 i, j;
MemCpy(h, _h, sizeof(U32) * 8);
/* 512-bit chunks is what we will operate on. */
U8 chunk[64];
buffer_state state;
init_buf_state(&state, input, len);
while (calc_chunk(chunk, &state)) {
U32 ah[8];
U8* p = chunk;
/* Initialize working variables to current hash value: */
for (i = 0; i < 8; i++)
ah[i] = h[i];
/* Compression function main loop: */
for (i = 0; i < 4; i++) {
/*
* The w-array is really w[64], but since we only need
* 16 of them at a time, we save stack by calculating
* 16 at a time.
*
* This optimization was not there initially and the
* rest of the comments about w[64] are kept in their
* initial state.
*/
/*
* create a 64-entry message schedule array w[0..63] of 32-bit words
* (The initial values in w[0..63] don't matter, so many implementations
* zero them here) copy chunk into first 16 words w[0..15] of the message
* schedule array
*/
U32 w[16];
U32 s0, s1;
for (j = 0; j < 16; j++) {
if (i == 0) {
w[j] = p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
p += 4;
} else {
/* Extend the first 16 words into the remaining 48 words w[16..63] of
* the message schedule array: */
s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ (w[(j + 1) & 0xf] >> 3);
s1 = right_rot(w[(j + 14) & 0xf], 17) ^ right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10);
w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1;
}
s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
U32 ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
U32 temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j];
s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
U32 maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
U32 temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
}
/* Add the compressed chunk to the current hash value: */
for (i = 0; i < 8; i++)
h[i] += ah[i];
}
/* Produce the final hash value (big-endian): */
for (i = 0, j = 0; i < 8; i++) {
hash[j++] = (h[i] >> 24);
hash[j++] = (h[i] >> 16);
hash[j++] = (h[i] >> 8);
hash[j++] = h[i];
}
}

133
System/Libraries/String.HC Normal file
View file

@ -0,0 +1,133 @@
U0 @string_append(U8* dst, U8* fmt, ...)
{
U8* buf;
if (argc) {
buf = StrPrintJoin(NULL, fmt, argc, argv);
} else {
buf = StrNew(fmt, adam_task);
}
U8* src = buf;
StrCpy(dst + StrLen(dst), src);
Free(buf);
}
Bool @string_is_number(U8* s)
{
while (*s) {
switch (*s) {
case '-':
case '.':
case '0' ... '9':
break;
default:
return FALSE;
break;
}
s++;
}
return TRUE;
}
Bool @string_begins_with(U8* fragment, U8* str)
{
if (!fragment || !str)
return FALSE;
if (StrLen(fragment) > StrLen(str))
return FALSE;
return !MemCmp(fragment, str, StrLen(fragment));
}
Bool @string_ends_with(U8* fragment, U8* str)
{
if (!fragment || !str)
return FALSE;
if (StrLen(fragment) > StrLen(str))
return FALSE;
return !MemCmp(fragment, str + StrLen(str) - StrLen(fragment), StrLen(fragment));
}
U8* @string_replace(U8* s, U8* oldW, U8* newW)
{
if (!StrFind(oldW, s)) {
return StrNew(s, adam_task);
}
U8* result;
I64 i, cnt = 0;
I64 newWlen = StrLen(newW);
I64 oldWlen = StrLen(oldW);
for (i = 0; s[i] != NULL; i++) {
if (StrFind(oldW, &s[i]) == &s[i]) {
cnt++;
i += oldWlen - 1;
}
}
result = MAlloc(i + cnt * (newWlen - oldWlen) + 1, adam_task);
i = 0;
while (*s) {
if (StrFind(oldW, s) == s) {
StrCpy(&result[i], newW);
i += newWlen;
s += oldWlen;
} else
result[i++] = *s++;
}
result[i] = NULL;
return result;
}
U8** @string_split(U8* s, U8 ch = '\n', I64* cnt)
{
U8 check_buf[4];
StrPrint(check_buf, "%c", ch);
if (!StrFind(check_buf, s)) {
U8** same_arr = CAlloc(sizeof(U8*) * 1, adam_task);
same_arr[0] = s;
*cnt = 1;
return same_arr;
}
U8* p = s;
cnt[0] = 0;
while (*p) {
if (*p == ch)
cnt[0]++;
p++;
}
if (!(cnt[0]))
return NULL;
cnt[0]++;
I64 i = -1;
U8** arr = CAlloc(sizeof(U8*) * cnt[0], adam_task);
p = s;
while (*p) {
if (*p == ch || i < 0) {
i++;
arr[i] = p;
if (*p == ch) {
arr[i]++;
*p = NULL;
}
}
p++;
}
return arr;
}
class @string
{
U0(*Append)
(U8 * dst, U8 * fmt, ...);
Bool (*BeginsWith)(U8* fragment, U8* str);
Bool (*EndsWith)(U8* fragment, U8* str);
Bool (*IsNumber)(U8* s);
U8* (*Replace)(U8* s, U8* oldW, U8* newW);
U8** (*Split)(U8* s, U8 ch = '\n', I64 * cnt);
};
@string String;
String.Append = &@string_append;
String.BeginsWith = &@string_begins_with;
String.EndsWith = &@string_ends_with;
String.IsNumber = &@string_is_number;
String.Replace = &@string_replace;
String.Split = &@string_split;

115
System/Libraries/Tlse.HC Normal file
View file

@ -0,0 +1,115 @@
#define TLS_V12 0x0303
Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions
U64 @tls_create_context(U8 is_server, U16 version)
{
U64 reg RDI rdi = is_server;
U64 reg RSI rsi = version;
no_warn rdi, rsi;
asm {
MOV RAX, TLS_CREATE_CONTEXT
CALL RAX
}
}
I32 @tls_sni_set(U64 context, U8* sni)
{
U64 reg RDI rdi = context;
U64 reg RSI rsi = sni;
no_warn rdi, rsi;
asm {
MOV RAX, TLS_SNI_SET
CALL RAX
}
}
I32 @tls_client_connect(U64 context)
{
U64 reg RDI rdi = context;
no_warn rdi;
asm {
MOV RAX, TLS_CLIENT_CONNECT
CALL RAX
}
}
U8* @tls_get_write_buffer(U64 context, U32* outlen)
{
U64 reg RDI rdi = context;
U64 reg RSI rsi = outlen;
no_warn rdi, rsi;
asm {
MOV RAX, TLS_GET_WRITE_BUFFER
CALL RAX
}
}
U0 @tls_buffer_clear(U64 context)
{
U64 reg RDI rdi = context;
no_warn rdi;
asm {
MOV RAX, TLS_BUFFER_CLEAR
CALL RAX
}
}
I32 @tls_connection_status(U64 context)
{
U64 reg RDI rdi = context;
no_warn rdi;
asm {
MOV RAX, TLS_CONNECTION_STATUS
CALL RAX
}
}
U0 @tls_consume_stream(U64 context, U8* buf, I32 buf_len, U64 certificate_verify)
{
U64 reg RDI rdi = context;
U64 reg RSI rsi = buf;
U64 reg RDX rdx = buf_len;
U64 reg RCX rcx = certificate_verify;
no_warn rdi, rsi, rdx, rcx;
asm {
MOV RAX, TLS_CONSUME_STREAM
CALL RAX
}
}
I32 @tls_read(U64 context, U8* buf, U32 size)
{
U64 reg RDI rdi = context;
U64 reg RSI rsi = buf;
U64 reg RDX rdx = size;
no_warn rdi, rsi, rdx;
asm {
MOV RAX, TLS_READ
CALL RAX
}
}
I32 @tls_write(U64 context, U8* data, U32 len)
{
U64 reg RDI rdi = context;
U64 reg RSI rsi = data;
U64 reg RDX rdx = len;
no_warn rdi, rsi, rdx;
asm {
MOV RAX, TLS_WRITE
CALL RAX
}
}
I32 @tls_established(U64 context)
{
U64 reg RDI rdi = context;
no_warn rdi;
asm {
MOV RAX, TLS_ESTABLISHED
CALL RAX
}
}
Silent(0);

56
System/MakeSystem.HC Normal file
View file

@ -0,0 +1,56 @@
/* clang-format off */
DocMax(adam_task);
WinMax(adam_task);
WinToTop(adam_task);
#include "Setup/Environment";
#include "Drivers/Virtio-blk";
// FFI support files
#include "FFI/Base";
#include "FFI/LibC";
#include "FFI/New";
#include "FFI/ELF64";
// stb_image library
#include "Utilities/Image";
load_elf("M:/build/bin/image");
// Jakt support files
#include "Jakt/OS";
#include "Jakt/IOPort";
#include "Jakt/PCI";
#include "Jakt/Time";
#include "Libraries/Tlse";
load_elf("M:/build/bin/tlse");
// Networking APIs
#include "Api/Dns.HC";
#include "Api/Icmp.HC";
#include "Api/Ipv4.HC";
#include "Api/MD5.HC";
#include "Api/NetInfo.HC";
#include "Api/Tcp.HC";
#include "Api/Tls.HC";
// Libraries
#include "Libraries/Base64";
#include "Libraries/Json";
#include "Libraries/Rsa";
#include "Libraries/Sha256";
#include "Libraries/String";
#include "Libraries/Http";
load_elf("M:/build/bin/net");
// Networking Utilities
#include "Utilities/Dns";
#include "Utilities/NetRep";
#include "Utilities/Ping";
#include "Utilities/Time";
Spawn(_start, , "Net Task");
/* clang-format on */

View file

@ -0,0 +1,98 @@
AutoComplete(0);
U0 @patch_call_rel32(U32 from, U32 to)
{
*(from(U8*)) = 0xE8;
*((from + 1)(I32*)) = to - from - 5;
}
U0 @patch_jmp_rel32(U32 from, U32 to)
{
*(from(U8*)) = 0xE9;
*((from + 1)(I32*)) = to - from - 5;
}
CMemBlk* ShrinkMemBlkByPags(CMemBlk* from, I64 count)
{
from->pags -= count;
U64 to = from;
to += count * MEM_PAG_SIZE;
MemCpy(to, from, MEM_PAG_SIZE);
return to;
}
U0 @sse_enable()
{
/* clang-format off */
asm
{
MOV_EAX_CR0
AND AX, 0xFFFB // clear coprocessor emulation CR0.EM
OR AX, 0x2 // set coprocessor monitoring CR0.MP
MOV_CR0_EAX
MOV_EAX_CR4
OR AX, 3 << 9 // set CR4.OSFXSR and CR4.OSXMMEXCPT at the same time
MOV_CR4_EAX
}
/* clang-format on */
}
U0 @sse_enable_on_all_cores()
{
I64 i;
for (i = 1; i < mp_cnt; i++)
Spawn(&@sse_enable, , , i);
}
I64 @t(Bool _condition, I64 _true, I64 _false)
{
if (_condition)
return _true;
return _false;
}
// Before doing anything else, we:
// 1. Mark memory in code heap below 0x1000000 as used.
sys_code_bp->mem_free_lst->next->pags = 0;
// 2. Free up 64MB at bottom of code heap for non-HolyC programs
sys_code_bp->mem_free_lst = ShrinkMemBlkByPags(sys_code_bp->mem_free_lst, 131072);
// 3. Enable SSE
@sse_enable;
U0 dd() { DocDump(adam_task->put_doc); }
//@patch_jmp_rel32(&Dbg2, &Reboot); // Reboot instead of crashing to the debugger
U0 NoBeep(I8, Bool) {};
@patch_jmp_rel32(&Beep, &NoBeep); // Don't delay on beep when entering debugger
Bool BlkDevLock2(CBlkDev* bd)
{
BlkDevChk(bd);
while (bd->lock_fwding)
bd = bd->lock_fwding;
if (!Bt(&bd->locked_flags, BDlf_LOCKED) || bd->owning_task != Fs) {
while (LBts(&bd->locked_flags, BDlf_LOCKED))
Sleep(Rand * 10);
bd->owning_task = Fs;
return TRUE;
} else
return FALSE;
}
Bool DrvLock2(CDrv* dv)
{
DrvChk(dv);
BlkDevLock2(dv->bd);
if (!Bt(&dv->locked_flags, DVlf_LOCKED) || dv->owning_task != Fs) {
while (LBts(&dv->locked_flags, DVlf_LOCKED))
Sleep(Rand * 10);
dv->owning_task = Fs;
return TRUE;
} else
return FALSE;
}
@patch_jmp_rel32(&BlkDevLock, &BlkDevLock2); // Patch BlkDevLock so we don't deadlock on multiple tasks reading from virtio disk
@patch_jmp_rel32(&DrvLock, &DrvLock2); // Patch DrvLock so we don't deadlock on multiple tasks reading from virtio disk

10
System/Utilities/Dns.HC Normal file
View file

@ -0,0 +1,10 @@
U0 DnsQuery(U8* host)
{
U32 result = @dns_query(host);
if (result == U32_MAX) {
"Error looking up host %s\n", host;
return;
}
"Query for %s: %d.%d.%d.%d\n", host, result.u8[3], result.u8[2], result.u8[1],
result.u8[0];
}

414
System/Utilities/Image.HC Normal file
View file

@ -0,0 +1,414 @@
Silent(1); // This is needed to suppress "Function should return val" warnings for wrappers to non-HolyC functions
class @image
{
CDC* (*FromBuffer)(U8* buffer, I64 len);
CDC* (*Load)(U8* filename);
CDC* (*Write)(U8* filename, CDC* dc);
};
@image Image;
class @image_frame
{
CDC* dc;
CSprite* sprite;
I64 delay;
};
class @image_collection
{
@image_frame** frames;
I64 count;
I64 current;
I64 jiffies;
I64 index;
@image_collection* next;
};
I64 @image_cbgr24_to_4_bit(CBGR24* ptr, Bool dither_probability)
{
I64 res, k;
if (dither_probability) {
k = RandU32;
if (SqrI64(ptr->r) + SqrI64(ptr->g) + SqrI64(ptr->b) >= 3 * SqrI64(k.u8[0]))
res = 8;
else
res = 0;
if (ptr->r >= k.u8[1])
res |= RED;
if (ptr->g >= k.u8[2])
res |= GREEN;
if (ptr->b >= k.u8[3])
res |= BLUE;
} else {
if (SqrI64(ptr->r) + SqrI64(ptr->g) + SqrI64(ptr->b) >= SqrI64(0x80)) {
res = 8;
if (ptr->r >= 0x80)
res |= RED;
if (ptr->g >= 0x80)
res |= GREEN;
if (ptr->b >= 0x80)
res |= BLUE;
} else {
res = 0;
if (ptr->r >= 0x40)
res |= RED;
if (ptr->g >= 0x40)
res |= GREEN;
if (ptr->b >= 0x40)
res |= BLUE;
}
}
return res;
}
#define IMAGE_DITHER_NONE 0
#define IMAGE_DITHER_NATIVE 1
#define IMAGE_DITHER_FLOYDSTEINBERG 2
U0 @image_render_4bit_floydstein(U8* buffer, I32 width, I32 height)
{
U64 reg RDI rdi = buffer;
U64 reg RSI rsi = width;
U64 reg RDX rdx = height;
no_warn rdi, rsi, rdx;
asm {
MOV RAX, RENDER_4BIT_FLOYDSTEIN
CALL RAX
}
}
CDC* @image_render_16color_native(U8* pixels, I32 x, I32 y, Bool dither)
{
I64 i;
I64 j;
I64 cnt = 0;
CBGR24 cbgr24;
CDC* dc = DCNew(x, y);
for (i = 0; i < y; i++)
for (j = 0; j < x; j++) {
cbgr24.r = pixels[cnt];
cbgr24.g = pixels[cnt + 1];
cbgr24.b = pixels[cnt + 2];
if (!pixels[cnt + 3])
dc->color = TRANSPARENT;
else
dc->color = @image_cbgr24_to_4_bit(&cbgr24, dither);
GrPlot(dc, j, y - i - 1);
cnt += 4;
}
return dc;
}
CBGR24 @image_palette_std[COLORS_NUM] = {
0x000000, 0x0000AA, 0x00AA00, 0x00AAAA,
0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
0x555555, 0x5555FF, 0x55FF55, 0x55FFFF,
0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF
};
CBGR24 @image_dif_rgb(CBGR24 from, CBGR24 to)
{
CBGR24 dif;
dif.r = to.r - from.r;
dif.g = to.g - from.g;
dif.b = to.b - from.b;
return dif;
}
F64 @image_dist_rgb(CBGR24 from, CBGR24 to)
{
CBGR24 dif = @image_dif_rgb(from, to);
F64 dist = dif.r * dif.r + dif.g * dif.g + dif.b * dif.b;
return dist;
}
I64 @image_get_4bit_color(CBGR24* cbgr24)
{
F64 dist = -1, tempDist;
I64 i;
I64 color = TRANSPARENT;
for (i = 0; i < COLORS_NUM; i++) {
tempDist = @image_dist_rgb(*cbgr24, @image_palette_std[i]);
if (tempDist < dist || dist < 0) {
dist = tempDist;
color = i;
}
}
return color;
}
CDC* @image_render_16color_floydsteinberg(U8* pixels, I32 width, I32 height)
{
@image_render_4bit_floydstein(pixels, width, height);
I64 i;
I64 j;
I64 cnt = 0;
CBGR24 cbgr24;
CDC* dc = DCNew(width, height);
for (i = 0; i < height; i++)
for (j = 0; j < width; j++) {
cbgr24.r = pixels[cnt];
cbgr24.g = pixels[cnt + 1];
cbgr24.b = pixels[cnt + 2];
if (!pixels[cnt + 3])
dc->color = TRANSPARENT;
else
dc->color = @image_get_4bit_color(&cbgr24);
GrPlot(dc, j, height - i - 1);
cnt += 4;
}
return dc;
}
CDC* @image_generate_dc_from_pixels(U8* pixels, I32 width, I32 height, Bool dither = IMAGE_DITHER_FLOYDSTEINBERG)
{
switch (dither) {
case IMAGE_DITHER_NONE:
case IMAGE_DITHER_NATIVE:
return @image_render_16color_native(pixels, width, height, dither);
break;
case IMAGE_DITHER_FLOYDSTEINBERG:
return @image_render_16color_floydsteinberg(pixels, width, height);
break;
default:
break;
}
return NULL;
}
U8* @image_load_gif_from_memory(U8* buffer, I64 len, I64** delays, I64* x, I64* y,
I64* z)
{
U64 reg RDI rdi = buffer;
U64 reg RSI rsi = len;
U64 reg RDX rdx = delays;
U64 reg RCX rcx = x;
U64 reg R8 r8 = y;
U64 reg R9 r9 = z;
no_warn rdi, rsi, rdx, rcx, r8, r9;
asm {
MOV RAX, IMAGE_LOAD_GIF_FROM_MEMORY
CALL RAX
}
}
U8* @stbi_failure_reason()
{
asm {
MOV RAX, STBI_FAILURE_REASON
CALL RAX
}
}
I32 @stbi_info_from_memory(U8* buffer, I64 len, I64* x, I64* y, I64* comp)
{
U64 reg RDI rdi = buffer;
U64 reg RSI rsi = len;
U64 reg RDX rdx = x;
U64 reg RCX rcx = y;
U64 reg R8 r8 = comp;
no_warn rdi, rsi, rdx, rcx, r8;
asm {
MOV RAX, STBI_INFO_FROM_MEMORY
CALL RAX
}
}
U8* @stbi_load_from_memory(U8* buffer, I64 len, I64* x, I64* y,
I64* channels_in_file, I64 desired_channels)
{
U64 reg RDI rdi = buffer;
U64 reg RSI rsi = len;
U64 reg RDX rdx = x;
U64 reg RCX rcx = y;
U64 reg R8 r8 = channels_in_file;
U64 reg R9 r9 = desired_channels;
no_warn rdi, rsi, rdx, rcx, r8, r9;
asm {
MOV RAX, STBI_LOAD_FROM_MEMORY
CALL RAX
}
}
U32* @stbi_write_png_to_mem(U32* pixels, I32 stride_bytes, I32 x, I32 y, I32 n, I32* out_len)
{
U64 reg RDI rdi = pixels;
U64 reg RSI rsi = stride_bytes;
U64 reg RDX rdx = x;
U64 reg RCX rcx = y;
U64 reg R8 r8 = n;
U64 reg R9 r9 = out_len;
no_warn rdi, rsi, rdx, rcx, r8, r9;
asm {
MOV RAX, STBI_WRITE_PNG_TO_MEM
CALL RAX
}
}
CDC* @image_load(U8* filename)
{
if (!filename || !FileFind(filename)) {
PrintErr("Image file not found.\n");
return NULL;
}
I64 len;
I32 x;
I32 y;
I32 comp;
U8* buffer = FileRead(filename, &len);
I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
if (code != 1) {
Free(buffer);
return NULL;
}
U8* pixels = @stbi_load_from_memory(buffer, len, &x, &y, &comp, 4);
Free(buffer);
CDC* dc = @image_generate_dc_from_pixels(pixels, x, y);
Free(pixels);
return dc;
}
U32 @image_rgba_color_table[16] = {
0xff000000, 0xffaa0000, 0xff00aa00, 0xffaaaa00,
0xff0000aa, 0xffaa00aa, 0xff0055aa, 0xffaaaaaa,
0xff555555, 0xffff5555, 0xff55ff55, 0xffffff55,
0xff5555ff, 0xffff55ff, 0xff55ffff, 0xffffffff
};
U32 @image_get_rgba_color(I64 color)
{
if (color > 15)
return 0;
return @image_rgba_color_table[color];
}
U32* @image_get_rgba_buffer_from_dc_body(CDC* dc)
{
if (!dc)
return NULL;
U32* pixels = CAlloc((dc->width * dc->height) * 4, adam_task);
I64 x;
I64 y;
I64 p = 0;
for (y = 0; y < dc->height; y++)
for (x = 0; x < dc->width; x++)
pixels[p++] = @image_get_rgba_color(GrPeek(dc, x, y));
return pixels;
}
U0 @image_write(U8* filename, CDC* dc)
{
if (!dc) {
PrintErr("Device context is NULL.\n");
return;
}
I32 out_len;
U32* rgba_buffer = @image_get_rgba_buffer_from_dc_body(dc);
if (!rgba_buffer) {
PrintErr("RGBA buffer is NULL.\n");
return;
}
U8* png_buffer = @stbi_write_png_to_mem(rgba_buffer, dc->width * 4, dc->width, dc->height, 4, &out_len);
if (!png_buffer) {
PrintErr("PNG buffer is NULL.\n");
Free(rgba_buffer);
return;
}
FileWrite(filename, png_buffer, out_len);
Free(rgba_buffer);
Free(png_buffer);
}
U32 @image_pixel_flip_rgb_bgr(U32 src)
{
U32 dst;
dst.u8[0] = src.u8[2];
dst.u8[1] = src.u8[1];
dst.u8[2] = src.u8[0];
dst.u8[3] = src.u8[3];
return dst;
}
CDC* @image_from_buffer(U8* buffer, I64 len)
{
I32 x = 0;
I32 y = 0;
U8* pixels = NULL;
CDC* dc = NULL;
I32 comp;
I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
if (code != 1) {
return NULL;
}
pixels = @stbi_load_from_memory(buffer, len, &x, &y, &comp, 4);
if (!pixels)
PopUpOk(@stbi_failure_reason);
dc = @image_generate_dc_from_pixels(pixels, x, y);
Free(pixels);
return dc;
}
@image_collection* @image_collection_from_buffer(U8* buffer, I64 len)
{
I64 i;
I32* delays;
I32 x;
I32 y;
I32 z;
I32 comp;
I32 code = @stbi_info_from_memory(buffer, len, &x, &y, &comp);
if (code != 1) {
return NULL;
}
U64 pixels = @image_load_gif_from_memory(buffer, len, &delays, &x, &y, &z);
if (!pixels)
PopUpOk(@stbi_failure_reason);
if (!z)
return NULL; // no frames?
@image_collection* collection = CAlloc(sizeof(@image_collection), adam_task);
@image_frame* frame;
collection->frames = CAlloc(sizeof(@image_frame*) * z, adam_task);
collection->count = z;
for (i = 0; i < z; i++) {
frame = CAlloc(sizeof(@image_frame), adam_task);
frame->dc = @image_generate_dc_from_pixels(pixels, x, y);
frame->sprite = DC2Sprite(frame->dc);
frame->delay = delays[i];
collection->frames[i] = frame;
pixels += (x * y) * 4;
}
return collection;
}
Image.FromBuffer = &@image_from_buffer;
Image.Load = &@image_load;
Image.Write = &@image_write;
Silent(0);
U0 Screenshot(U8* custom_filename = NULL, Bool output_filename_to_focus_task = FALSE)
{
CDC* dc = DCScrnCapture;
U8 filename[256];
CDateStruct ds;
if (custom_filename)
StrCpy(filename, custom_filename);
else {
Date2Struct(&ds, Now);
StrPrint(filename, "C:/Tmp/ScrnShots/%04d-%02d-%02d-%02d-%02d-%02d.png", ds.year, ds.mon, ds.day_of_mon, ds.hour, ds.min, ds.sec);
}
Image.Write(filename, dc);
DCDel(dc);
if (output_filename_to_focus_task)
XTalk(sys_focus_task, filename);
};
U0 @screenshot_hotkey(I64)
{
Screenshot("C:/Home/Screenshot.png", TRUE);
}
CtrlAltCBSet('S', &@screenshot_hotkey, "", , FALSE);

View file

@ -0,0 +1,22 @@
U0 NetRep()
{
NetInfoRequest* req = @net_info_request;
"MAC address : %02x:%02x:%02x:%02x:%02x:%02x\n", req->mac_address.u8[5], req->mac_address.u8[4],
req->mac_address.u8[3], req->mac_address.u8[2],
req->mac_address.u8[1], req->mac_address.u8[0];
"IPv4 address : %d.%d.%d.%d\n", req->ipv4_address.u8[3], req->ipv4_address.u8[2],
req->ipv4_address.u8[1], req->ipv4_address.u8[0];
"IPv4 netmask : %d.%d.%d.%d\n", req->ipv4_netmask.u8[3], req->ipv4_netmask.u8[2],
req->ipv4_netmask.u8[1], req->ipv4_netmask.u8[0];
"IPv4 network : %d.%d.%d.%d\n", req->ipv4_network.u8[3], req->ipv4_network.u8[2],
req->ipv4_network.u8[1], req->ipv4_network.u8[0];
"IPv4 gateway : %d.%d.%d.%d\n", req->ipv4_gateway.u8[3], req->ipv4_gateway.u8[2],
req->ipv4_gateway.u8[1], req->ipv4_gateway.u8[0];
"DNS server (port) : %d.%d.%d.%d (%d)\n", req->dns_server_address.u8[3], req->dns_server_address.u8[2],
req->dns_server_address.u8[1], req->dns_server_address.u8[0], req->dns_server_port;
"RX bytes : %d\n", req->rx_bytes;
"RX frames : %d\n", req->rx_frames;
"TX bytes : %d\n", req->tx_bytes;
"TX frames : %d\n", req->tx_frames;
Free(req);
}

72
System/Utilities/Ping.HC Normal file
View file

@ -0,0 +1,72 @@
#define PING_ERR_INVALID_HOST 1
#define PING_ERR_HOST_NOT_FOUND 2
#define PING_PAYLOAD_SIZE 56
I64 @ping_err(I64 code)
{
switch (code) {
case PING_ERR_INVALID_HOST:
"Invalid host specified\n";
return 1;
break;
case PING_ERR_HOST_NOT_FOUND:
"Host not found\n";
return 2;
break;
default:
"Unspecified error\n";
return -1;
}
}
I64 Ping(U8* host, I64 count = 4)
{
if (!host)
return @ping_err(PING_ERR_INVALID_HOST);
if (!StrLen(host))
return @ping_err(PING_ERR_INVALID_HOST);
U32 addr = @dns_query(host);
if (addr == U32_MAX)
return @ping_err(PING_ERR_HOST_NOT_FOUND);
U16 iden = (RandU16 * SysTimerRead) & 0xFFFF;
I64 start_jiffies;
U32 reply = NULL;
I64 res = 0;
U16 seq = 0;
I64 loss = 0;
IcmpRequest* request = CAlloc(sizeof(IcmpRequest), Fs->code_heap);
"PING %s (%d.%d.%d.%d): %d data bytes\n",
host, addr.u8[3], addr.u8[2], addr.u8[1], addr.u8[0], PING_PAYLOAD_SIZE;
I64 i;
for (i = 0; i < count; i++) {
start_jiffies = cnts.jiffies;
reply = @icmp_echo_request(addr, iden, seq, request, i);
if (!reply) {
"Request timeout for icmp_seq %d\n", seq;
++loss;
res = 1;
} else {
"%d bytes from %d.%d.%d.%d: icmp_seq=%d ttl=%d time=%d ms\n",
reply.u16[1], addr.u8[3], addr.u8[2], addr.u8[1], addr.u8[0], seq, reply.u16[0], cnts.jiffies - start_jiffies;
}
while (cnts.jiffies < start_jiffies + 1000 && i < (count - 1))
Sleep(1);
++seq;
}
Free(request);
"--- %d.%d.%d.%d ping statistics ---\n", addr.u8[3], addr.u8[2], addr.u8[1], addr.u8[0];
"%d packets transmitted, %d packets received, %0f",
seq, seq - loss, (loss * 1.0 / seq * 1.0) * 100;
PutChars(37);
" packet loss\n";
return res;
}

112
System/Utilities/Time.HC Normal file
View file

@ -0,0 +1,112 @@
U0 @time_cmos_update_byte(I64 time_reg, I64 val)
{
OutU8(0x70, time_reg);
OutU8(0x71, val);
}
I64 @time_dec_to_bcd(I64 val)
{
return (((val / 10) << 4) | (val % 10));
}
U0 @time_update(U8* date_str, I64 mS_delta, I64 hour_offset)
{
no_warn mS_delta;
Bool is_bcd;
OutU8(0x70, 0x0B);
if (InU8(0x71) & 4)
is_bcd = FALSE;
else
is_bcd = TRUE;
I64 date_argc;
U8** date_argv = String.Split(date_str, ' ', &date_argc);
I64 month = DefineMatch(date_argv[2], "ST_MONTHS") + 1;
I64 day = Str2I64(date_argv[1]);
I64 year = Str2I64(date_argv[3] + 2);
I64 century = 20;
date_argv[4][2] = NULL;
date_argv[4][5] = NULL;
I64 hour = Str2I64(date_argv[4]);
I64 minute = Str2I64(date_argv[4] + 3);
I64 second = Str2I64(date_argv[4] + 6);
// FIXME: Handle month boundaries, and 12 hour time
hour += hour_offset;
if (hour < 0) {
hour += 24;
--day;
} else if (hour > 23) {
hour -= 24;
++day;
}
if (is_bcd) {
century = @time_dec_to_bcd(century);
year = @time_dec_to_bcd(year);
month = @time_dec_to_bcd(month);
day = @time_dec_to_bcd(day);
hour = @time_dec_to_bcd(hour);
minute = @time_dec_to_bcd(minute);
second = @time_dec_to_bcd(second);
}
@time_cmos_update_byte(0x32, century);
@time_cmos_update_byte(0x09, year);
@time_cmos_update_byte(0x08, month);
@time_cmos_update_byte(0x07, day);
@time_cmos_update_byte(0x04, hour);
@time_cmos_update_byte(0x02, minute);
@time_cmos_update_byte(0x00, second);
}
Bool @time_in_dst()
{
CDateStruct ds;
Date2Struct(&ds, Now);
if (ds.mon > 3 && ds.mon < 11)
return TRUE;
if (ds.mon < 3 || ds.mon > 11)
return FALSE;
if (ds.mon == 3 && ds.day_of_mon != 8)
return ds.day_of_mon > 8;
if (ds.mon == 3)
return ds.hour > 1;
if (ds.mon == 11 && ds.day_of_mon != 1)
return FALSE;
if (ds.mon == 11)
return ds.hour < 2;
}
I64 @time_tz_offset()
{
if (@time_in_dst)
return -4;
return -5;
}
U0 @time_query(Bool set = FALSE)
{
U8 buf[1024];
@http_url* url = @http_parse_url("http://time.google.com");
@http_response* resp = Http.Head(url, &buf);
while (resp->state != HTTP_STATE_DONE)
Sleep(1);
I64 mS_delta = cnts.jiffies;
"Set current date and time to %s ", Json.Get(resp->headers, "Date");
if (!set)
set = YorN;
else
"\n";
if (set)
@time_update(Json.Get(resp->headers, "Date"), mS_delta, @time_tz_offset);
}
U0 TimeSync()
{
Sleep(500);
@time_query(1);
}

188
scripts/build-all Executable file
View file

@ -0,0 +1,188 @@
#!/usr/bin/python3
from pathlib import Path
import glob
import os
import subprocess
import sys
import time
if len(sys.argv) < 2:
raise ValueError('wrong number of arguments')
project_path = sys.argv[1] + '/'
project_name = project_path.rsplit('/')[-2]
isoc_file = project_path + 'build/isoc/Slon.ISO.C'
redsea_path = project_path + 'build/redsea'
home_path = str(Path.home()) + '/'
jakt_compiler_path = home_path + 'cloned/jakt/build/bin/jakt'
jakt_runtime_path = home_path + 'cloned/jakt/runtime'
jakt_lib_path = home_path + 'cloned/jakt/build/lib/x86_64-unknown-linux-unknown/'
qemu_slipstream_iso_file = project_path + 'build/isoc/bootable.iso'
qemu_virtio_disk_path = home_path + 'virtio-disk.qcow2'
qemu_bin_path = home_path + "/Programs/qemu-9.1.2/build/qemu-system-x86_64"
qemu_display = "-display sdl,grab-mod=rctrl"
templeos_iso_file = home_path + 'iso/TempleOS.ISO'
qemu_run_cmd = qemu_bin_path + ' ' + qemu_display + ' -enable-kvm -m 1024 -netdev tap,id=mynet0,ifname=tap0,script=no,downscript=no -device virtio-net,netdev=mynet0 -drive file=' + qemu_virtio_disk_path + ',format=qcow2,if=none,index=0,media=disk,id=virtio-disk -device virtio-blk-pci,drive=virtio-disk -cdrom ' + qemu_slipstream_iso_file + ' -debugcon stdio -boot d'
def clang_format_src_files():
print("build-all: clang-format-src-files")
exclude_paths = ["stb_", "tlse", ".iso.c"]
format_file_extensions = [".c", ".cpp", ".h", ".hc"]
for src_file in glob.glob(project_path + "**", recursive=True):
exclude_file = False
for exclude_path in exclude_paths:
if src_file.lower().find(exclude_path) > 0:
exclude_file = True
if exclude_file:
continue
for format_file_extension in format_file_extensions:
if src_file.lower().endswith(format_file_extension):
print(src_file)
res = os.system('clang-format -i --style=file:' + project_path + '.clang-format ' + src_file)
if res:
raise ValueError("build-all: step 'clang-format-src-files' failed, error code " + str(res))
def refresh_build_path():
print("build-all: refresh-build-path")
res = os.system('rm -rf ' + project_path + 'build && mkdir -p ' + project_path + 'build/bin && mkdir -p ' + project_path + 'build/isoc && mkdir -p ' + project_path + 'build/lib && mkdir -p ' + project_path + 'build/redsea')
if res:
raise ValueError("build-all: step 'refresh-build-path' failed, error code " + str(res))
def build_image():
print("build-all: build-image")
build_specific_options = '-Wl,--section-start=.text=0x1004000 -Wl,--section-start=.plt=0x1002020 -no-pie'
res = os.system('cd ' + project_path + '&& cd src/image && gcc -o ../../build/bin/image ' + build_specific_options + ' -O0 -mno-mmx -mno-red-zone image.c')
if res:
raise ValueError("build-all: step 'build-image' failed, error code " + str(res))
def build_libtemple():
print("build-all: build-libtemple")
res = os.system('cd ' + project_path + 'src/libtemple && g++ -c -o ../../build/libtemple.o libtemple.cpp && gcc -shared -o ../../build/lib/libtemple.so ../../build/libtemple.o && rm ' + project_path + 'build/libtemple.o')
if res:
raise ValueError("build-all: step 'build-libtemple' failed, error code " + str(res))
def build_tlse():
print("build-all: build-tlse")
build_specific_options = '-Wl,--section-start=.text=0x1204000 -Wl,--section-start=.plt=0x1202020 -no-pie'
res = os.system('cd ' + project_path + '&& cd src/tlse && gcc -o ../../build/bin/tlse ' + build_specific_options + ' -O0 -mno-mmx -mno-red-zone -DTLS_AMALGAMATION tlse.c')
if res:
raise ValueError("build-all: step 'build-tlse' failed, error code " + str(res))
def transpile_net_to_sepples():
print("build-all: transpile-net-to-sepples")
res = os.system('cd ' + project_path + 'src/net && ' + jakt_compiler_path + ' -S -R ' + jakt_runtime_path + ' -B ' + project_path + 'build/net -O net.jakt')
if res:
raise ValueError("build-all: step 'transpile-net-to-sepples' failed, error code " + str(res))
def build_net():
print("build-all: build-net")
build_specific_options = '-Wno-invalid-offsetof -Wl,--section-start=.text=0x1404000 -Wl,--section-start=.plt=0x1402020 -no-pie'
res = os.system('cd ' + project_path + 'build/net && clang++-19 ' + build_specific_options + ' -O3 -I ' + jakt_runtime_path + ' -I ' + project_path + '/src/libtemple -fcolor-diagnostics -std=c++20 -fno-exceptions -Wno-user-defined-literals -Wno-deprecated-declarations -Wno-parentheses-equality -Wno-unqualified-std-cast-call -Wno-unknown-warning-option -Wno-int-to-pointer-cast -mno-red-zone -o ../bin/net *.cpp ../lib/libtemple.so ' + jakt_lib_path + 'libjakt_runtime_x86_64-unknown-linux-unknown.a ' + jakt_lib_path + 'libjakt_main_x86_64-unknown-linux-unknown.a && cd .. && rm -rf net')
if res:
raise ValueError("build-all: step 'build-net' failed, error code " + str(res))
def address_string_for_symbol(file, symbol):
p = subprocess.Popen('readelf -s --wide "' + file + '" | grep \'' + symbol + '$\' | awk \'{sub("000000000", "0x", $2); print $2}\'', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
return str(p.communicate()[0][:-1].decode(encoding='utf-8'))
def image_hc_fixup(macro, symbol, image_bin_path, image_hc_path):
os.system('echo -e "#define ' + macro + ' ' + address_string_for_symbol(image_bin_path, symbol) + '\n" | cat - ' + image_hc_path + ' | sponge ' + image_hc_path)
return
def tlse_hc_fixup(macro, symbol, tlse_bin_path, tlse_hc_path):
os.system('echo -e "#define ' + macro + ' ' + address_string_for_symbol(tlse_bin_path, symbol) + '\n" | cat - ' + tlse_hc_path + ' | sponge ' + tlse_hc_path)
return
def generate_iso_c_file():
print("build-all: generate-iso-c-file")
step_error_message = "build-all: step 'generate-iso-c-file' failed, error code "
res = os.system('isoc-mount --rw ' + isoc_file + ' ' + redsea_path)
if res:
raise ValueError(step_error_message + str(res))
time.sleep(0.25)
copy_files_cmd_line = 'rsync -av --inplace --progress ' + project_path + ' ' + redsea_path
copy_files_cmd_line += ' --exclude .clang-format'
copy_files_cmd_line += ' --exclude .git'
copy_files_cmd_line += ' --exclude .gitignore'
copy_files_cmd_line += ' --exclude .vscode'
copy_files_cmd_line += ' --exclude build/isoc'
copy_files_cmd_line += ' --exclude build/lib'
copy_files_cmd_line += ' --exclude build/redsea'
copy_files_cmd_line += ' --exclude scripts'
copy_files_cmd_line += ' --exclude src'
res = os.system(copy_files_cmd_line)
if res:
raise ValueError(step_error_message + str(res))
# Fixup addresses for Image.HC
image_bin_path = redsea_path + '/build/bin/image'
image_hc_path = redsea_path + '/System/Utilities/Image.HC'
image_hc_fixup('IMAGE_LOAD_GIF_FROM_MEMORY', 'image_load_gif_from_memory', image_bin_path, image_hc_path)
image_hc_fixup('STBI_WRITE_PNG_TO_MEM', 'stbi_write_png_to_mem', image_bin_path, image_hc_path)
image_hc_fixup('STBI_LOAD_FROM_MEMORY', 'stbi_load_from_memory', image_bin_path, image_hc_path)
image_hc_fixup('STBI_INFO_FROM_MEMORY', 'stbi_info_from_memory', image_bin_path, image_hc_path)
image_hc_fixup('STBI_FAILURE_REASON', 'stbi_failure_reason', image_bin_path, image_hc_path)
image_hc_fixup('RENDER_4BIT_FLOYDSTEIN', 'render_4bit_floydstein', image_bin_path, image_hc_path)
# Fixup addresses for Tlse.HC
rsa_hc_path = redsea_path + '/System/Libraries/Rsa.HC'
tlse_bin_path = redsea_path + '/build/bin/tlse'
tlse_hc_path = redsea_path + '/System/Libraries/Tlse.HC'
tlse_hc_fixup('RSA_IMPORT', 'rsa_import', tlse_bin_path, rsa_hc_path)
tlse_hc_fixup('RSA_CREATE_SIGNATURE', 'rsa_create_signature', tlse_bin_path, rsa_hc_path)
tlse_hc_fixup('RSA_VERIFY_SIGNATURE', 'rsa_verify_signature', tlse_bin_path, rsa_hc_path)
tlse_hc_fixup('TLS_CREATE_CONTEXT', 'tls_create_context', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_SNI_SET', 'tls_sni_set', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_CLIENT_CONNECT', 'tls_client_connect', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_CONNECTION_STATUS', 'tls_connection_status', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_GET_WRITE_BUFFER', 'tls_get_write_buffer', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_BUFFER_CLEAR', 'tls_buffer_clear', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_CONSUME_STREAM', 'tls_consume_stream', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_READ', 'tls_read', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_WRITE', 'tls_write', tlse_bin_path, tlse_hc_path)
tlse_hc_fixup('TLS_ESTABLISHED', 'tls_established', tlse_bin_path, tlse_hc_path)
time.sleep(0.25)
res = os.system('sync && fusermount -u ' + redsea_path)
if res:
raise ValueError(step_error_message + str(res))
time.sleep(0.25)
def generate_slipstream_iso_file():
print("build-all: generate-slipstream-iso-file")
res = os.system('templeos-slipstream ' + templeos_iso_file + ' ' + isoc_file + ' ' + qemu_slipstream_iso_file)
if res:
raise ValueError("build-all: step 'generate-slipstream-iso-file' failed, error code " + str(res))
def run():
print("build-all: run")
res = os.system(qemu_run_cmd)
if res:
raise ValueError("build-all: step 'run' failed, error code " + str(res))
def build_all():
clang_format_src_files()
#refresh_build_path()
#build_image()
#build_libtemple()
#build_tlse()
#transpile_net_to_sepples()
#build_net()
generate_iso_c_file()
generate_slipstream_iso_file()
run()
build_all()

209
src/image/image.c Normal file
View file

@ -0,0 +1,209 @@
#define STBI_WRITE_NO_STDIO
#define STB_IMAGE_WRITE_STATIC
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_IMPLEMENTATION
#define STBI_NO_LINEAR
#define STBI_NO_STDIO
#define STBI_NO_SIMD
#define STBI_NO_HDR
#include "stb_image.h"
#include "stb_image_write.h"
int main() { return 0; }
STBIDEF stbi_uc* image_load_gif_from_memory(stbi_uc const* buffer, int len,
int** delays, int* x, int* y,
int* z)
{
int comp;
return stbi_load_gif_from_memory(buffer, len, delays, x, y, z, &comp, 4);
}
/* dither.c: MIT License
Copyright (c) 2016 jonmortiboy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
typedef struct RGB {
int r;
int g;
int b;
} RGB;
int imgw, imgh;
// Define the 4bit colour palette
int numCols = 16;
RGB cols4bit[] = {
{ 0, 0, 0 }, { 0, 0, 170 }, { 0, 170, 0 }, { 0, 170, 170 },
{ 170, 0, 0 }, { 170, 0, 170 }, { 170, 85, 0 }, { 170, 170, 170 },
{ 85, 85, 85 }, { 85, 85, 255 }, { 85, 255, 85 }, { 85, 255, 255 },
{ 255, 85, 85 }, { 255, 85, 255 }, { 255, 255, 85 }, { 255, 255, 255 }
};
RGB* cols = cols4bit;
RGB getRGB(uint32_t* pixels, int x, int y);
void setRGB(uint32_t* pixels, int x, int y, RGB rgb);
RGB difRGB(RGB from, RGB to);
RGB addRGB(RGB a, RGB b);
RGB divRGB(RGB rgb, double d);
RGB mulRGB(RGB rgb, double d);
RGB nearestRGB(RGB rgb, RGB* rgbs, int numRGBs);
double distRGB(RGB from, RGB to);
void render_4bit_floydstein(uint32_t* pixels, int width, int height);
RGB getRGB(uint32_t* pixels, int x, int y)
{
RGB rgb;
rgb.r = 0;
rgb.g = 0;
rgb.b = 0;
if (x < 0 || x >= imgw || y < 0 || y >= imgh)
return rgb;
rgb.r = (pixels[y * imgw + x] & 0xff);
rgb.g = (pixels[y * imgw + x] & 0xff00) >> 8;
rgb.b = (pixels[y * imgw + x] & 0xff0000) >> 16;
return rgb;
}
void setRGB(uint32_t* pixels, int x, int y, RGB rgb)
{
if (x < 0 || x >= imgw || y < 0 || y >= imgh)
return;
uint32_t alpha = pixels[y * imgw + x] & 0xff000000;
pixels[y * imgw + x] = alpha + (rgb.r) + (rgb.g << 8) + (rgb.b << 16);
}
RGB difRGB(RGB from, RGB to)
{
RGB dif;
dif.r = to.r - from.r;
dif.g = to.g - from.g;
dif.b = to.b - from.b;
return dif;
}
RGB addRGB(RGB a, RGB b)
{
RGB sum;
sum.r = a.r + b.r;
sum.g = a.g + b.g;
sum.b = a.b + b.b;
if (sum.r > 255)
sum.r = 255;
if (sum.r < 0)
sum.r = 0;
if (sum.g > 255)
sum.g = 255;
if (sum.g < 0)
sum.g = 0;
if (sum.b > 255)
sum.b = 255;
if (sum.b < 0)
sum.b = 0;
return sum;
}
RGB divRGB(RGB rgb, double d)
{
RGB div;
div.r = (int)((double)rgb.r / d);
div.g = (int)((double)rgb.g / d);
div.b = (int)((double)rgb.b / d);
return div;
}
RGB mulRGB(RGB rgb, double d)
{
RGB mul;
mul.r = (int)((double)rgb.r * d);
mul.g = (int)((double)rgb.g * d);
mul.b = (int)((double)rgb.b * d);
return mul;
}
double distRGB(RGB from, RGB to)
{
RGB dif = difRGB(from, to);
double dist = dif.r * dif.r + dif.g * dif.g + dif.b * dif.b;
return dist;
}
RGB nearestRGB(RGB rgb, RGB rgbs[], int numRGBs)
{
double dist = -1, tempDist;
RGB nearest;
int i;
for (i = 0; i < numRGBs; i++) {
tempDist = distRGB(rgb, rgbs[i]);
if (tempDist < dist || dist < 0) {
dist = tempDist;
nearest = rgbs[i];
}
}
return nearest;
}
void render_4bit_floydstein(uint32_t* pixels, int width, int height)
{
int i, x, y;
imgw = width;
imgh = height;
RGB rgb, nearest, rgberror;
for (i = 0; i < imgw * imgh; i++) {
rgb = getRGB(pixels, i % imgw, i / imgw);
nearest = nearestRGB(rgb, cols, numCols);
rgberror = difRGB(nearest, rgb);
rgberror = divRGB(rgberror, 16);
x = i % imgw;
y = i / imgw;
setRGB(pixels, x + 1, y,
addRGB(getRGB(pixels, x + 1, y), mulRGB(rgberror, 7)));
setRGB(pixels, x - 1, y + 1,
addRGB(getRGB(pixels, x - 1, y + 1), mulRGB(rgberror, 3)));
setRGB(pixels, x, y + 1,
addRGB(getRGB(pixels, x, y + 1), mulRGB(rgberror, 5)));
setRGB(pixels, x + 1, y + 1,
addRGB(getRGB(pixels, x + 1, y + 1), rgberror));
setRGB(pixels, i % imgw, i / imgw, nearest);
}
}

8632
src/image/stb_image.h Normal file

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more