Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 19 additions & 13 deletions .github/workflows/go1.25.yml → .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
name: Go 1.25 Build and Test
name: Build & Test

on:
push:
branches: [ "main" ]
branches: [main]
pull_request:
branches: [ "main" ]
branches: [main]
workflow_dispatch:

jobs:
permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
services:
Expand All @@ -25,15 +28,18 @@ jobs:
env:
FMSG_TEST_DATABASE_URL: postgres://postgres@localhost:5432/postgres?sslmode=disable
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v7

- name: Set up Go
uses: actions/setup-go@v7
with:
go-version: "1.27.x"

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: '1.25'
- name: Build
run: go build ./...

- name: Build
run: go build -v ./...
- name: Test
run: go test -race ./...

- name: Test
run: go test -race ./...
- name: Vet
run: go vet ./...
6 changes: 5 additions & 1 deletion .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ jobs:
}
DOCKER_REF=$(matching_ref fmsg-docker)
WEBAPI_REF=$(matching_ref fmsg-webapi)
ID_REF=$(matching_ref fmsgid)
CLI_REF=$(matching_ref fmsg-cli)
DISPATCH_STARTED=$(date -u +%Y-%m-%dT%H:%M:%SZ)

# Coordinated schema/API changes use their companion branches.
gh workflow run integration-test.yml \
--repo markmnl/fmsg-docker \
--ref "$DOCKER_REF" \
-f fmsgd_ref="$FMSGD_REF" \
-f fmsg_webapi_ref="$WEBAPI_REF"
-f fmsg_webapi_ref="$WEBAPI_REF" \
-f fmsgid_ref="$ID_REF" \
-f fmsg_cli_ref="$CLI_REF"

echo "Triggered integration test for fmsgd_ref=$FMSGD_REF, polling for run..."

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
[![Go 1.25](https://github.com/markmnl/fmsgd/actions/workflows/go1.25.yml/badge.svg)](https://github.com/markmnl/fmsgd/actions/workflows/go1.25.yml)
[![Build & Test](https://github.com/markmnl/fmsgd/actions/workflows/build-test.yml/badge.svg?branch=main)](https://github.com/markmnl/fmsgd/actions/workflows/build-test.yml?query=branch%3Amain)
[![Go 1.27+](https://img.shields.io/badge/Go-1.27%2B-00ADD8?logo=go&logoColor=white)](https://go.dev/dl/)

# fmsgd

Implementation of [fmsg](https://github.com/markmnl/fmsg) host written in Go! Uses local filesystem and PostgreSQL database to store messages.

## Building from source

Tested with Go 1.25 on Linux and Windows, AMD64 and ARM
Requires Go 1.27 or newer.

1. Clone this repository
2. Run `go build ./cmd/fmsgd/`
Expand Down
2 changes: 1 addition & 1 deletion cmd/fmsg-backfill/migrate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ func TestMigrationFailureRollsBackEverything(t *testing.T) {
func TestDecodeHeaderRejectsTruncation(t *testing.T) {
h := prepared(t, rawMessage(t, "example.com", strings.Repeat("content ", 200)))
data := h.Encode()
for i := 0; i < len(data); i++ {
for i := range data {
if _, err := decodeHeader(data[:i]); err == nil {
t.Fatalf("accepted prefix %d", i)
}
Expand Down
9 changes: 4 additions & 5 deletions cmd/fmsgd/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net"
"net/http"
"os"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -127,11 +128,9 @@ func verifyDomainIP(domain string) {
log.Panicf("ERROR: failed to lookup fmsg.%s: %s", domain, err)
}

for _, ip := range authorisedIPs {
if externalIP.Equal(ip) {
log.Printf("INFO: external IP %s found in fmsg.%s authorised IPs", externalIP, domain)
return
}
if slices.ContainsFunc(authorisedIPs, externalIP.Equal) {
log.Printf("INFO: external IP %s found in fmsg.%s authorised IPs", externalIP, domain)
return
}

log.Panicf("ERROR: external IP %s not found in fmsg.%s authorised IPs %v", externalIP, domain, authorisedIPs)
Expand Down
7 changes: 3 additions & 4 deletions cmd/fmsgd/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"net/url"
"os"
"path/filepath"
"slices"
"strings"
"time"
"unicode"
Expand Down Expand Up @@ -559,10 +560,8 @@ func verifySenderIP(c net.Conn, senderDomain string) error {
return fmt.Errorf("DNS verification failed")
}

for _, ip := range authorisedIPs {
if remoteIP.Equal(ip) {
return nil
}
if slices.ContainsFunc(authorisedIPs, remoteIP.Equal) {
return nil
}

log.Printf("WARN: remote IP %s not in authorised IPs for fmsg.%s", remoteIP.String(), senderDomain)
Expand Down
2 changes: 1 addition & 1 deletion cmd/fmsgd/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func postMsgStat(addr *FMsgAddress, timestamp float64, size int, isSending bool)
}
uri := fmt.Sprintf("%s/fmsgid/%s", IDURI, part)

payload := map[string]interface{}{
payload := map[string]any{
"address": addr.ToString(),
"ts": timestamp,
"size": size}
Expand Down
2 changes: 1 addition & 1 deletion cmd/fmsgd/sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func lockPendingRecipients(tx *sql.Tx, table string, msgID int64, domain string,
AND (r.response_code IS NULL OR r.response_code = ANY($5))
AND (r.time_last_attempt IS NULL OR ($2 - r.time_last_attempt) > LEAST($3 * POWER(2.0, GREATEST(r.attempt_count - 1, 0)::float), $4))
AND ($2 - %s) < $4`, table, joinBatch, ageRef)
args := []interface{}{msgID, now, RetryInterval, RetryMaxAge, pq.Array(retryableResponseCodes)}
args := []any{msgID, now, RetryInterval, RetryMaxAge, pq.Array(retryableResponseCodes)}
if table == "msg_add_to" {
q += " AND r.batch_id = $6"
args = append(args, batchID)
Expand Down
10 changes: 5 additions & 5 deletions cmd/fmsgd/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,8 +378,8 @@ func attachAddToRecipients(tx *sql.Tx, msgID int64, msg *FMsgHeader) error {
// domains are recorded with localResponseCodeNotOurDelivery so the
// sender's pending queries never treat them as our delivery work.
for _, addr := range msg.To {
var delivered interface{}
var code interface{}
var delivered any
var code any
if addr.Domain == Domain {
delivered = now
} else {
Expand Down Expand Up @@ -416,8 +416,8 @@ on conflict (msg_id, addr) do nothing`, msgID, addr.ToString(), delivered, code)
}

for _, addr := range msg.AddTo {
var delivered interface{}
var code interface{}
var delivered any
var code any
if addr.Domain == Domain {
delivered = now
} else {
Expand Down Expand Up @@ -450,7 +450,7 @@ func sealReceivedBatch(tx *sql.Tx, id int64, h *FMsgHeader, hash []byte) error {
// this host rejected keep the per-recipient code it responded; recipients on
// other domains are recorded for participant checks and faithful message
// reconstruction (SPEC §10.3/§11) but are another host's delivery duty.
func inboundRecipientRow(addr FMsgAddress, localOutcome map[string]uint8, now float64) (delivered interface{}, code interface{}) {
func inboundRecipientRow(addr FMsgAddress, localOutcome map[string]uint8, now float64) (delivered any, code any) {
c, ok := localOutcome[strings.ToLower(addr.ToString())]
if !ok {
return nil, int16(localResponseCodeNotOurDelivery)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/markmnl/fmsgd

go 1.25
go 1.27.0

require (
github.com/caitlinelfring/go-env-default v1.1.0
Expand Down
5 changes: 1 addition & 4 deletions pkg/fmsg/deflate.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ const deflateSampleSize = 8192
// and reports whether the ratio looks promising (compressed < 80% of input).
// src is seeked back to the start on return.
func probeSample(src *os.File, srcSize uint32) (bool, error) {
sampleLen := int64(deflateSampleSize)
if int64(srcSize) < sampleLen {
sampleLen = int64(srcSize)
}
sampleLen := min(int64(srcSize), int64(deflateSampleSize))

var buf bytes.Buffer
zw := zlib.NewWriter(&buf)
Expand Down