Basic Fujinet network support, iis-tracker works
This commit is contained in:
parent
9f36507fa5
commit
33ea1f6e7a
|
@ -75,7 +75,7 @@ func (c *CardSmartPort) assign(a *Apple2, slot int) {
|
||||||
a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address
|
a.mmu.Peek(0x44), a.mmu.Peek(0x45), // data address
|
||||||
0,
|
0,
|
||||||
})
|
})
|
||||||
} else if command == proDosDeviceCommandRead || command == proDosDeviceCommandWrite {
|
} else if command == proDosDeviceCommandReadBlock || command == proDosDeviceCommandWriteBlock {
|
||||||
call = newSmartPortCallSynthetic(c, command, []uint8{
|
call = newSmartPortCallSynthetic(c, command, []uint8{
|
||||||
3, // 3args
|
3, // 3args
|
||||||
unit,
|
unit,
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
package fujinet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
See:
|
||||||
|
https://github.com/FujiNetWIFI/fujinet-platformio/tree/master/lib/fnjson
|
||||||
|
https://github.com/FujiNetWIFI/fujinet-platformio/wiki/JSON-Query-Format
|
||||||
|
*/
|
||||||
|
|
||||||
|
type FnJson struct {
|
||||||
|
data any
|
||||||
|
Result []uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFnJson() *FnJson {
|
||||||
|
var js FnJson
|
||||||
|
return &js
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *FnJson) Parse(data []uint8) ErrorCode {
|
||||||
|
// See FNJSON::parse()
|
||||||
|
err := json.Unmarshal(data, &js.data)
|
||||||
|
if err != nil {
|
||||||
|
return NetworkErrorJsonParseError
|
||||||
|
}
|
||||||
|
return NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js *FnJson) Query(query []uint8) {
|
||||||
|
// See FNJSON::setReadQuery
|
||||||
|
// See https://github.com/kbranigan/cJSON/blob/master/cJSON_Utils.c
|
||||||
|
if query[len(query)-1] == 0 {
|
||||||
|
query = query[0 : len(query)-1]
|
||||||
|
}
|
||||||
|
queryString := string(query)
|
||||||
|
queryString = strings.TrimSuffix(queryString, "/0")
|
||||||
|
queryString = strings.TrimPrefix(queryString, "/")
|
||||||
|
queryString = strings.TrimSuffix(queryString, "/")
|
||||||
|
path := strings.Split(queryString, "/")
|
||||||
|
|
||||||
|
js.Result = nil
|
||||||
|
current := js.data
|
||||||
|
for i := 0; i < len(path); i++ {
|
||||||
|
switch v := current.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
var found bool
|
||||||
|
current, found = v[path[i]]
|
||||||
|
if !found {
|
||||||
|
// Not found
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
index, err := strconv.Atoi(path[i])
|
||||||
|
if err != nil {
|
||||||
|
// Path for arrays should be an int
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if index < 0 || index >= len(v) {
|
||||||
|
// Index out of bounds
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current = v[index]
|
||||||
|
default:
|
||||||
|
// It's a leaf. We can't go down
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
js.Result = getJsonValue(current)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJsonValue(data any) []uint8 {
|
||||||
|
// See FNJson::getValue
|
||||||
|
if data == nil {
|
||||||
|
return []uint8("NULL")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v := data.(type) {
|
||||||
|
case bool:
|
||||||
|
if v {
|
||||||
|
return []uint8("TRUE")
|
||||||
|
} else {
|
||||||
|
return []uint8("FALSE")
|
||||||
|
}
|
||||||
|
case float64:
|
||||||
|
//if math.Floor(v) == v { // As done in FNJSON__getValue()
|
||||||
|
// It's an integer
|
||||||
|
return []uint8(strconv.Itoa(int(v)))
|
||||||
|
//} else {
|
||||||
|
// return []uint8(fmt.Sprintf("%.10f", v))
|
||||||
|
//}
|
||||||
|
case string:
|
||||||
|
return []uint8(v)
|
||||||
|
case []any:
|
||||||
|
s := make([]uint8, 0)
|
||||||
|
for i := 0; i < len(v); i++ {
|
||||||
|
s = append(s, getJsonValue(v[i])...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
case map[string]any:
|
||||||
|
s := make([]uint8, 0)
|
||||||
|
for k, e := range v {
|
||||||
|
s = append(s, []uint8(k)...)
|
||||||
|
s = append(s, getJsonValue(e)...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
default:
|
||||||
|
// Should not be possible for an object unmarshalled from a JSON
|
||||||
|
return []uint8("UNKNOWN")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
package fujinet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testQuerys(t *testing.T, message string, queries [][]string) {
|
||||||
|
js := NewFnJson()
|
||||||
|
errorCode := js.Parse([]uint8(message))
|
||||||
|
|
||||||
|
if errorCode != NoError {
|
||||||
|
t.Fatalf("Parse error %v. It should be %v", errorCode, NoError)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pair := range queries {
|
||||||
|
js.Query([]uint8(pair[0]))
|
||||||
|
result := string(js.Result)
|
||||||
|
if result != pair[1] {
|
||||||
|
t.Errorf("Query for %s, returned %s. It should be %s", pair[0], result, pair[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryMap(t *testing.T) {
|
||||||
|
// See https://github.com/FujiNetWIFI/fujinet-apps/blob/master/iss-tracker/apple2/src/satellite.c
|
||||||
|
testMessage := `{"timestamp": 1667218311, "message": "success", "iss_position": {"latitude": "21.3276", "longitude": "-39.4989"}}`
|
||||||
|
|
||||||
|
testCases := [][]string{
|
||||||
|
{"/iss_position/longitude", "-39.4989"},
|
||||||
|
{"/iss_position/latitude", "21.3276"},
|
||||||
|
{"/timestamp", "1667218311"},
|
||||||
|
}
|
||||||
|
testQuerys(t, testMessage, testCases)
|
||||||
|
}
|
||||||
|
|
||||||
|
// See https://github.com/FujiNetWIFI/fujinet-apps/blob/master/json-test/atari/jsontest.c
|
||||||
|
const testArrayMessage = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "108361296757279278",
|
||||||
|
"created_at": "2022-05-25T07:00:06.000Z",
|
||||||
|
"in_reply_to_id": null,
|
||||||
|
"in_reply_to_account_id": null,
|
||||||
|
"sensitive": false,
|
||||||
|
"spoiler_text": "",
|
||||||
|
"visibility": "public",
|
||||||
|
"language": "en",
|
||||||
|
"uri": "https://botsin.space/users/osxthemes/statuses/108361286061805267",
|
||||||
|
"url": "https://botsin.space/@osxthemes/108361286061805267",
|
||||||
|
"replies_count": 0,
|
||||||
|
"reblogs_count": 0,
|
||||||
|
"favourites_count": 0,
|
||||||
|
"edited_at": null,
|
||||||
|
"local_only": null,
|
||||||
|
"content": "<p>Floppies! - Robert Davis</p>",
|
||||||
|
"reblog": null,
|
||||||
|
"account": {
|
||||||
|
"id": "23439",
|
||||||
|
"username": "osxthemes",
|
||||||
|
"acct": "osxthemes@botsin.space",
|
||||||
|
"display_name": "Macintosh Themes",
|
||||||
|
"locked": false,
|
||||||
|
"bot": true,
|
||||||
|
"discoverable": true,
|
||||||
|
"group": false,
|
||||||
|
"created_at": "2018-03-28T00:00:00.000Z",
|
||||||
|
"note": "<p>I tweet Mac OSX (pre-10.5) and Kaleidoscope (Classic) themes. Bot by <span class=\"h-card\"><a href=\"https://octodon.social/@Eramdam\" class=\"u-url mention\" rel=\"nofollow noopener noreferrer\" target=\"_blank\">@<span>Eramdam</span></a></span>, inspired by kaleidoscopemac@twitter.com. Also on Twitter at <a href=\"https://twitter.com/osxthemes\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">twitter.com/osxthemes</span><span class=\"invisible\"></span></a></p>",
|
||||||
|
"url": "https://botsin.space/@osxthemes",
|
||||||
|
"avatar": "https://assets.oldbytes.space/assets.oldbytes.space/accounts/avatars/000/023/439/original/322ac0c621b55624.png",
|
||||||
|
"avatar_static": "https://assets.oldbytes.space/assets.oldbytes.space/accounts/avatars/000/023/439/original/322ac0c621b55624.png",
|
||||||
|
"header": "https://assets.oldbytes.space/assets.oldbytes.space/cache/accounts/headers/000/023/439/original/ea0e0cd513b5a9f7.png",
|
||||||
|
"header_static": "https://assets.oldbytes.space/assets.oldbytes.space/cache/accounts/headers/000/023/439/original/ea0e0cd513b5a9f7.png",
|
||||||
|
"followers_count": 157,
|
||||||
|
"following_count": 1,
|
||||||
|
"statuses_count": 17615,
|
||||||
|
"last_status_at": "2022-05-25",
|
||||||
|
"emojis": [],
|
||||||
|
"fields": []
|
||||||
|
},
|
||||||
|
"media_attachments": [
|
||||||
|
{
|
||||||
|
"id": "108361296738754794",
|
||||||
|
"type": "image",
|
||||||
|
"url": "https://assets.oldbytes.space/assets.oldbytes.space/cache/media_attachments/files/108/361/296/738/754/794/original/5785ab0a51d0db1f.gif",
|
||||||
|
"preview_url": "https://assets.oldbytes.space/assets.oldbytes.space/cache/media_attachments/files/108/361/296/738/754/794/small/5785ab0a51d0db1f.png",
|
||||||
|
"remote_url": "https://files.botsin.space/media_attachments/files/108/361/285/793/211/606/original/7fe52f343cf0c99a.gif",
|
||||||
|
"preview_remote_url": null,
|
||||||
|
"text_url": null,
|
||||||
|
"meta": {
|
||||||
|
"original": {
|
||||||
|
"width": 213,
|
||||||
|
"height": 181,
|
||||||
|
"size": "213x181",
|
||||||
|
"aspect": 1.1767955801104972
|
||||||
|
},
|
||||||
|
"small": {
|
||||||
|
"width": 213,
|
||||||
|
"height": 181,
|
||||||
|
"size": "213x181",
|
||||||
|
"aspect": 1.1767955801104972
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "Floppies! - Robert Davis",
|
||||||
|
"blurhash": "UbLNcMO@QkAAx{jJX4V@8yX9xYX7D@kXoZkV"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mentions": [],
|
||||||
|
"tags": [],
|
||||||
|
"emojis": [],
|
||||||
|
"card": null,
|
||||||
|
"poll": null
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
|
||||||
|
func TestQueryArray(t *testing.T) {
|
||||||
|
|
||||||
|
testCases := [][]string{
|
||||||
|
{"/0/account/display_name", "Macintosh Themes"},
|
||||||
|
{"/0/created_at", "2022-05-25T07:00:06.000Z"},
|
||||||
|
{"/0/content", "<p>Floppies! - Robert Davis</p>"},
|
||||||
|
{"/0/nonexistent", "NULL"},
|
||||||
|
{"/1/account/display_name", "NULL"},
|
||||||
|
{"/-1/account/display_name", "NULL"},
|
||||||
|
{"/zz/account/display_name", "NULL"},
|
||||||
|
{"/0/media_attachments/0/meta/original", "width213height181size213x181aspect1.1767955801"},
|
||||||
|
}
|
||||||
|
testQuerys(t, testArrayMessage, testCases)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package fujinet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Protocol interface {
|
||||||
|
Open(urlParsed *url.URL)
|
||||||
|
Close()
|
||||||
|
ReadAll() ([]uint8, ErrorCode)
|
||||||
|
Write(data []uint8) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorCode uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// See fujinet-platformio/lib/network-protocol/status_error_codes.h
|
||||||
|
NoError = ErrorCode(0)
|
||||||
|
NetworkErrorEndOfFile = ErrorCode(136)
|
||||||
|
NetworkErrorGeneral = ErrorCode(144)
|
||||||
|
NetworkErrorNotImplemented = ErrorCode(146)
|
||||||
|
NetworkErrorInvalidDeviceSpec = ErrorCode(165)
|
||||||
|
|
||||||
|
// New
|
||||||
|
NetworkErrorJsonParseError = ErrorCode(250)
|
||||||
|
)
|
||||||
|
|
||||||
|
func InstantiateProtocol(urlParsed *url.URL, method uint8) (Protocol, ErrorCode) {
|
||||||
|
scheme := strings.ToUpper(urlParsed.Scheme)
|
||||||
|
switch scheme {
|
||||||
|
case "HTTP":
|
||||||
|
return newProtocolHttp(method), NoError
|
||||||
|
case "HTTPS":
|
||||||
|
return newProtocolHttp(method), NoError
|
||||||
|
default:
|
||||||
|
return nil, NetworkErrorGeneral
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package fujinet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type protocolHttp struct {
|
||||||
|
method uint8
|
||||||
|
url *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProtocolHttp(method uint8) *protocolHttp {
|
||||||
|
var p protocolHttp
|
||||||
|
p.method = method
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *protocolHttp) Open(urlParsed *url.URL) {
|
||||||
|
p.url = urlParsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *protocolHttp) Close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *protocolHttp) ReadAll() ([]uint8, ErrorCode) {
|
||||||
|
if p.method == 12 /*GET*/ {
|
||||||
|
resp, err := http.Get(p.url.String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, NetworkErrorGeneral
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NetworkErrorGeneral
|
||||||
|
}
|
||||||
|
return data, NoError
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, NetworkErrorNotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *protocolHttp) Write(data []uint8) error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -227,10 +227,6 @@ func (mmu *memoryManager) accessWrite(address uint16) memoryHandler {
|
||||||
func (mmu *memoryManager) peekWord(address uint16) uint16 {
|
func (mmu *memoryManager) peekWord(address uint16) uint16 {
|
||||||
return uint16(mmu.Peek(address)) +
|
return uint16(mmu.Peek(address)) +
|
||||||
uint16(mmu.Peek(address+1))<<8
|
uint16(mmu.Peek(address+1))<<8
|
||||||
|
|
||||||
//return uint16(mmu.Peek(address)) +
|
|
||||||
// 0x100*uint16(mmu.Peek(address+1))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek returns the data on the given address
|
// Peek returns the data on the given address
|
||||||
|
@ -274,6 +270,12 @@ func (mmu *memoryManager) PeekCode(address uint16) uint8 {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mmu *memoryManager) pokeRange(address uint16, data []uint8) {
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
mmu.Poke(address+uint16(i), data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Poke sets the data at the given address
|
// Poke sets the data at the given address
|
||||||
func (mmu *memoryManager) Poke(address uint16, value uint8) {
|
func (mmu *memoryManager) Poke(address uint16, value uint8) {
|
||||||
mh := mmu.accessWrite(address)
|
mh := mmu.accessWrite(address)
|
||||||
|
|
|
@ -11,10 +11,16 @@ type smartPortDevice interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
proDosDeviceCommandStatus = 0
|
proDosDeviceCommandStatus = 0
|
||||||
proDosDeviceCommandRead = 1
|
proDosDeviceCommandReadBlock = 1
|
||||||
proDosDeviceCommandWrite = 2
|
proDosDeviceCommandWriteBlock = 2
|
||||||
proDosDeviceCommandFormat = 3
|
proDosDeviceCommandFormat = 3
|
||||||
|
proDosDeviceCommandControl = 4
|
||||||
|
proDosDeviceCommandInit = 5
|
||||||
|
proDosDeviceCommandOpen = 6
|
||||||
|
proDosDeviceCommandClose = 7
|
||||||
|
proDosDeviceCommandRead = 8
|
||||||
|
proDosDeviceCommandWrite = 9
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -103,20 +109,57 @@ func (spc *smartPortCall) param24(offset uint8) uint32 {
|
||||||
uint32(spc.param8(offset+2))<<16
|
uint32(spc.param8(offset+2))<<16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (spc *smartPortCall) paramData(offset uint8) []uint8 {
|
||||||
|
address := uint16(spc.param8(offset)) +
|
||||||
|
uint16(spc.param8(offset+1))<<8
|
||||||
|
|
||||||
|
size := spc.host.a.mmu.peekWord(address)
|
||||||
|
|
||||||
|
data := make([]uint8, size)
|
||||||
|
for i := 0; i < int(size); i++ {
|
||||||
|
data[i] = spc.host.a.mmu.Peek(address + 2 + uint16(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
func (spc *smartPortCall) String() string {
|
func (spc *smartPortCall) String() string {
|
||||||
switch spc.command {
|
switch spc.command {
|
||||||
case proDosDeviceCommandStatus:
|
case proDosDeviceCommandStatus:
|
||||||
return fmt.Sprintf("STATUS(%v, unit=%v, code=%v)",
|
return fmt.Sprintf("STATUS(%v, unit=%v, code=%v)",
|
||||||
spc.command, spc.unit(),
|
spc.command, spc.unit(),
|
||||||
spc.statusCode())
|
spc.statusCode())
|
||||||
|
case proDosDeviceCommandReadBlock:
|
||||||
|
return fmt.Sprintf("READBLOCK(%v, unit=%v, block=%v)",
|
||||||
|
spc.command, spc.unit(),
|
||||||
|
spc.param24(4))
|
||||||
|
case proDosDeviceCommandWriteBlock:
|
||||||
|
return fmt.Sprintf("WRITEBLOCK(%v, unit=%v, block=%v)",
|
||||||
|
spc.command, spc.unit(),
|
||||||
|
spc.param24(4))
|
||||||
|
case proDosDeviceCommandControl:
|
||||||
|
return fmt.Sprintf("CONTROL(%v, unit=%v, code=%v)",
|
||||||
|
spc.command, spc.unit(),
|
||||||
|
spc.param8(4))
|
||||||
|
case proDosDeviceCommandInit:
|
||||||
|
return fmt.Sprintf("INIT(%v, unit=%v)",
|
||||||
|
spc.command, spc.unit())
|
||||||
|
case proDosDeviceCommandOpen:
|
||||||
|
return fmt.Sprintf("OPEN(%v, unit=%v)",
|
||||||
|
spc.command, spc.unit())
|
||||||
|
case proDosDeviceCommandClose:
|
||||||
|
return fmt.Sprintf("CLOSE(%v, unit=%v)",
|
||||||
|
spc.command, spc.unit())
|
||||||
case proDosDeviceCommandRead:
|
case proDosDeviceCommandRead:
|
||||||
return fmt.Sprintf("READ(%v, unit=%v, block=%v)",
|
return fmt.Sprintf("READ(%v, unit=%v, pos=%v, len=%v)",
|
||||||
spc.command, spc.unit(),
|
spc.command, spc.unit(),
|
||||||
spc.param24(4))
|
spc.param24(6),
|
||||||
|
spc.param16(4))
|
||||||
case proDosDeviceCommandWrite:
|
case proDosDeviceCommandWrite:
|
||||||
return fmt.Sprintf("WRITE(%v, unit=%v, block=%v)",
|
return fmt.Sprintf("WRITE(%v, unit=%v, pos=%v, len=%v)",
|
||||||
spc.command, spc.unit(),
|
spc.command, spc.unit(),
|
||||||
spc.param24(4))
|
spc.param24(6),
|
||||||
|
spc.param16(4))
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("UNKNOWN(%v, unit=%v)",
|
return fmt.Sprintf("UNKNOWN(%v, unit=%v)",
|
||||||
|
|
|
@ -2,6 +2,10 @@ package izapple2
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ivanizag/izapple2/fujinet"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -17,12 +21,22 @@ See:
|
||||||
type SmartPortFujinet struct {
|
type SmartPortFujinet struct {
|
||||||
host *CardSmartPort // For DMA
|
host *CardSmartPort // For DMA
|
||||||
trace bool
|
trace bool
|
||||||
|
|
||||||
|
protocol fujinet.Protocol
|
||||||
|
jsonChannelMode bool
|
||||||
|
statusByte uint8
|
||||||
|
errorCode fujinet.ErrorCode
|
||||||
|
|
||||||
|
jsonData *fujinet.FnJson
|
||||||
|
data []uint8
|
||||||
|
//connected uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSmartPortFujinet creates a new fujinet device
|
// NewSmartPortFujinet creates a new fujinet device
|
||||||
func NewSmartPortFujinet(host *CardSmartPort) *SmartPortFujinet {
|
func NewSmartPortFujinet(host *CardSmartPort) *SmartPortFujinet {
|
||||||
var d SmartPortFujinet
|
var d SmartPortFujinet
|
||||||
d.host = host
|
d.host = host
|
||||||
|
d.errorCode = fujinet.NoError
|
||||||
|
|
||||||
return &d
|
return &d
|
||||||
}
|
}
|
||||||
|
@ -31,19 +45,27 @@ func (d *SmartPortFujinet) exec(call *smartPortCall) uint8 {
|
||||||
var result uint8
|
var result uint8
|
||||||
|
|
||||||
switch call.command {
|
switch call.command {
|
||||||
|
|
||||||
|
case proDosDeviceCommandOpen:
|
||||||
|
result = proDosDeviceNoError
|
||||||
|
|
||||||
|
case proDosDeviceCommandClose:
|
||||||
|
result = proDosDeviceNoError
|
||||||
|
|
||||||
case proDosDeviceCommandStatus:
|
case proDosDeviceCommandStatus:
|
||||||
address := call.param16(2)
|
address := call.param16(2)
|
||||||
result = d.status(call.statusCode(), address)
|
result = d.status(call.statusCode(), address)
|
||||||
|
|
||||||
|
case proDosDeviceCommandControl:
|
||||||
|
data := call.paramData(2)
|
||||||
|
controlCode := call.param8(4)
|
||||||
|
result = d.control(data, controlCode)
|
||||||
|
|
||||||
case proDosDeviceCommandRead:
|
case proDosDeviceCommandRead:
|
||||||
address := call.param16(2)
|
address := call.param16(2)
|
||||||
block := call.param24(4)
|
len := call.param16(4)
|
||||||
result = d.readBlock(block, address)
|
pos := call.param24(6)
|
||||||
|
result = d.read(pos, len, address)
|
||||||
case proDosDeviceCommandWrite:
|
|
||||||
address := call.param16(2)
|
|
||||||
block := call.param24(4)
|
|
||||||
result = d.writeBlock(block, address)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Prodos device command not supported
|
// Prodos device command not supported
|
||||||
|
@ -58,63 +80,167 @@ func (d *SmartPortFujinet) exec(call *smartPortCall) uint8 {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SmartPortFujinet) readBlock(block uint32, dest uint16) uint8 {
|
func (d *SmartPortFujinet) read(pos uint32, length uint16, dest uint16) uint8 {
|
||||||
if d.trace {
|
if d.trace {
|
||||||
fmt.Printf("[SmartPortFujinet] Read block %v into $%x.\n", block, dest)
|
fmt.Printf("[SmartPortFujinet] Read %v bytes from pos %v into $%x.\n",
|
||||||
|
length, pos, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// Byte by byte transfer to memory using the full Poke code path
|
||||||
|
for i := uint16(0); i < uint16(len(d.data)) && i < length; i++ {
|
||||||
|
d.host.a.mmu.Poke(dest+i, d.data[i])
|
||||||
|
}
|
||||||
|
|
||||||
return proDosDeviceNoError
|
return proDosDeviceNoError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *SmartPortFujinet) writeBlock(block uint32, source uint16) uint8 {
|
func (d *SmartPortFujinet) control(data []uint8, code uint8) uint8 {
|
||||||
if d.trace {
|
switch code {
|
||||||
fmt.Printf("[SmartPortFujinet] Write block %v from $%x.\n", block, source)
|
case 'O':
|
||||||
|
// Open URL
|
||||||
|
method := data[0]
|
||||||
|
translation := data[1]
|
||||||
|
url := data[2:]
|
||||||
|
d.controlOpen(method, translation, string(url))
|
||||||
|
|
||||||
|
case 'P':
|
||||||
|
if d.jsonChannelMode {
|
||||||
|
d.controlJsonParse()
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Q':
|
||||||
|
if d.jsonChannelMode {
|
||||||
|
d.controlJsonQuery(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 0xfc:
|
||||||
|
mode := data[0]
|
||||||
|
d.controlChannelMode(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
|
|
||||||
return proDosDeviceNoError
|
return proDosDeviceNoError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *SmartPortFujinet) controlJsonParse() {
|
||||||
|
// See FNJSON::parse()
|
||||||
|
if d.trace {
|
||||||
|
fmt.Printf("[SmartPortFujinet] control-parse()\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, errorCode := d.protocol.ReadAll()
|
||||||
|
if errorCode != fujinet.NoError {
|
||||||
|
d.errorCode = errorCode
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.jsonData = fujinet.NewFnJson()
|
||||||
|
d.errorCode = d.jsonData.Parse(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SmartPortFujinet) controlJsonQuery(query []uint8) {
|
||||||
|
if d.trace {
|
||||||
|
fmt.Printf("[SmartPortFujinet] control-query('%s')\n", query)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.jsonData != nil {
|
||||||
|
d.jsonData.Query(query)
|
||||||
|
d.data = d.jsonData.Result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SmartPortFujinet) controlChannelMode(mode uint8) {
|
||||||
|
// See iwmNetwork::channel_mode()
|
||||||
|
if d.trace {
|
||||||
|
fmt.Printf("control-channel-mode(%v)\n", mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == 0 {
|
||||||
|
d.jsonChannelMode = false
|
||||||
|
} else if mode == 1 {
|
||||||
|
d.jsonChannelMode = true
|
||||||
|
}
|
||||||
|
// The rest of the cases do not change the mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *SmartPortFujinet) controlOpen(method uint8, translation uint8, rawUrl string) {
|
||||||
|
// See iwmNetwork::open()
|
||||||
|
if d.trace {
|
||||||
|
fmt.Printf("[SmartPortFujinet] control-open(%v, %v, '%s'\n", method, translation, rawUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.protocol != nil {
|
||||||
|
d.protocol.Close()
|
||||||
|
d.protocol = nil
|
||||||
|
}
|
||||||
|
d.statusByte = 0
|
||||||
|
|
||||||
|
// Remove "N:" prefix
|
||||||
|
rawUrl = strings.TrimPrefix(rawUrl, "N:")
|
||||||
|
|
||||||
|
urlParsed, err := url.Parse(rawUrl)
|
||||||
|
if err != nil {
|
||||||
|
d.errorCode = fujinet.NetworkErrorInvalidDeviceSpec
|
||||||
|
d.statusByte = 4 //client_error
|
||||||
|
}
|
||||||
|
|
||||||
|
d.protocol, d.errorCode = fujinet.InstantiateProtocol(urlParsed, method)
|
||||||
|
if d.protocol == nil {
|
||||||
|
d.statusByte = 4 //client_error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
d.protocol.Open(urlParsed)
|
||||||
|
d.jsonChannelMode = false
|
||||||
|
}
|
||||||
|
|
||||||
func (d *SmartPortFujinet) status(code uint8, dest uint16) uint8 {
|
func (d *SmartPortFujinet) status(code uint8, dest uint16) uint8 {
|
||||||
|
|
||||||
switch code {
|
switch code {
|
||||||
case prodosDeviceStatusCodeDevice:
|
case prodosDeviceStatusCodeDevice:
|
||||||
// See iwmNetwork::encode_status_reply_packet()
|
// See iwmNetwork::encode_status_reply_packet()
|
||||||
d.host.a.mmu.Poke(dest, prodosDeviceStatusCodeTypeRead&prodosDeviceStatusCodeTypeOnline)
|
d.host.a.mmu.pokeRange(dest, []uint8{
|
||||||
d.host.a.mmu.Poke(dest+1, 0x00)
|
prodosDeviceStatusCodeTypeRead & prodosDeviceStatusCodeTypeOnline,
|
||||||
d.host.a.mmu.Poke(dest+2, 0x00)
|
0, 0, 0, // Block size is 0
|
||||||
d.host.a.mmu.Poke(dest+3, 0x00) // Block size is 0
|
})
|
||||||
|
|
||||||
case prodosDeviceStatusCodeDeviceInfo:
|
case prodosDeviceStatusCodeDeviceInfo:
|
||||||
// See iwmNetwork::encode_status_reply_packet()
|
// See iwmNetwork::encode_status_reply_packet()
|
||||||
d.host.a.mmu.Poke(dest, prodosDeviceStatusCodeTypeRead&prodosDeviceStatusCodeTypeOnline)
|
d.host.a.mmu.pokeRange(dest, []uint8{
|
||||||
d.host.a.mmu.Poke(dest+1, 0x00)
|
prodosDeviceStatusCodeTypeRead & prodosDeviceStatusCodeTypeOnline,
|
||||||
d.host.a.mmu.Poke(dest+2, 0x00)
|
0, 0, 0, // Block size is 0
|
||||||
d.host.a.mmu.Poke(dest+3, 0x00) // Block size is 0
|
7, 'N', 'E', 'T', 'W', 'O', 'R', 'K', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
|
||||||
d.host.a.mmu.Poke(dest+4, 0x07) // Name length
|
0x02, // Type hard disk
|
||||||
d.host.a.mmu.Poke(dest+5, 'N')
|
0x00, // Subtype network (comment in network.cpp has 0x0a)
|
||||||
d.host.a.mmu.Poke(dest+6, 'E')
|
0x00, 0x01, // Firmware version
|
||||||
d.host.a.mmu.Poke(dest+7, 'T')
|
})
|
||||||
d.host.a.mmu.Poke(dest+8, 'W')
|
|
||||||
d.host.a.mmu.Poke(dest+9, 'O')
|
case 'R':
|
||||||
d.host.a.mmu.Poke(dest+10, 'R')
|
// Net read, do nothing
|
||||||
d.host.a.mmu.Poke(dest+11, 'K')
|
|
||||||
d.host.a.mmu.Poke(dest+12, ' ')
|
case 'S':
|
||||||
d.host.a.mmu.Poke(dest+13, ' ')
|
// Get connection status
|
||||||
d.host.a.mmu.Poke(dest+14, ' ')
|
len := len(d.data)
|
||||||
d.host.a.mmu.Poke(dest+15, ' ')
|
if d.jsonChannelMode {
|
||||||
d.host.a.mmu.Poke(dest+16, ' ')
|
// See FNJSON
|
||||||
d.host.a.mmu.Poke(dest+17, ' ')
|
errorCode := 0
|
||||||
d.host.a.mmu.Poke(dest+18, ' ')
|
if len == 0 {
|
||||||
d.host.a.mmu.Poke(dest+19, ' ')
|
errorCode = int(fujinet.NetworkErrorEndOfFile)
|
||||||
d.host.a.mmu.Poke(dest+20, ' ')
|
}
|
||||||
d.host.a.mmu.Poke(dest+20, 0x02) // Type hard disk
|
d.host.a.mmu.pokeRange(dest, []uint8{
|
||||||
d.host.a.mmu.Poke(dest+20, 0x00) // Subtype network (comment in network.cpp has 0x0a)
|
uint8(len & 0xff),
|
||||||
d.host.a.mmu.Poke(dest+23, 0x00)
|
uint8((len >> 8) & 0xff),
|
||||||
d.host.a.mmu.Poke(dest+24, 0x01) // Firmware
|
1, /*True*/
|
||||||
|
uint8(errorCode),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// TODO
|
||||||
|
d.host.a.mmu.pokeRange(dest, []uint8{
|
||||||
|
uint8(len & 0xff),
|
||||||
|
uint8((len >> 8) & 0xff),
|
||||||
|
1, // ?? d.connected,
|
||||||
|
uint8(d.errorCode),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return proDosDeviceNoError // The return code is always success
|
return proDosDeviceNoError // The return code is always success
|
||||||
|
|
|
@ -46,12 +46,12 @@ func (d *SmartPortHardDisk) exec(call *smartPortCall) uint8 {
|
||||||
address := call.param16(2)
|
address := call.param16(2)
|
||||||
result = d.status(address)
|
result = d.status(address)
|
||||||
|
|
||||||
case proDosDeviceCommandRead:
|
case proDosDeviceCommandReadBlock:
|
||||||
address := call.param16(2)
|
address := call.param16(2)
|
||||||
block := call.param24(4)
|
block := call.param24(4)
|
||||||
result = d.readBlock(block, address)
|
result = d.readBlock(block, address)
|
||||||
|
|
||||||
case proDosDeviceCommandWrite:
|
case proDosDeviceCommandWriteBlock:
|
||||||
address := call.param16(2)
|
address := call.param16(2)
|
||||||
block := call.param24(4)
|
block := call.param24(4)
|
||||||
result = d.writeBlock(block, address)
|
result = d.writeBlock(block, address)
|
||||||
|
|
Loading…
Reference in New Issue